windows下c#程序提高实时性

引言:

勇哥手里的运控软件对实时性有了新要求。

因为同设备上运行的其它视觉软件对cpu时间有变态的占用,压缩了运控软件的cpu占用时间。

我们知道如果视觉软件卡一下,最多是处理时间长一点,但是运控软件卡一下,那就意味着撞机的问题,这个要严重得多。



这个问题会持续把研究结果更新到本贴子。


(一)提升线程和进程的优先级别


ProcessPriorityClass等级说明

public enum ProcessPriorityClass


字段

AboveNormal32768

指定进程的优先级高于 Normal 但低于 High。


BelowNormal16384

指定进程的优先级在 Idle 之上,但在 Normal 之下。


High128

指定进程执行必须立即执行的时间关键任务,如 Task List 对话框,不管操作系统的负荷如何,用户调用该对话框后均必须迅速响应。 该进程的线程优先于普通或空闲优先级类进程的线程。

为进程的优先级类指定 High 这一个优先级时需谨慎,因为高优先级类应用程序几乎可以使用所有可用的处理器时间。


Idle64

指定此进程的线程只能在系统空闲时运行,如屏幕保护程序。 更高优先级类中运行的任何进程的线程都优先于此进程的线程。 此优先级类由子进程继承。


Normal32

指定进程没有特殊的安排需求。


RealTime256

指定进程拥有可能的最高优先级。

具有 RealTime 优先级的进程的线程抢占所有其他进程的线程,包括执行重要任务的操作系统进程。 因此,执行时间并不太短的 RealTime 优先级进程可能导致磁盘高速缓存不刷新或鼠标无响应。


设置优先级:


//设置当前程序的进程优先级

           var prop = System.Diagnostics.Process.GetCurrentProcess();

           prop.PriorityClass = System.Diagnostics.ProcessPriorityClass.RealTime;

任务管理器查看进程优先级




下面引用一篇博文,提到马上要谈到的第二点“多媒体定时器”

--------------------------------------------------------------------------

此文是在实现modbus-RTU通讯的基础上总结而来的,主要讲述了如何提高window做工业控制时的实时性能。
PC机上做控制系统,一般就是在window或者linux操作系统上做控制系统软件开发,window上做控制系统,有如下优势:
1)现有的设备驱动支持
2)各类厂家提供的现成的卡板
3)以及比较平民化的开发平台
4)数量众多的开发人员。

2 提高实时性手段
window是一个非实时操作系统,如果要做实时任务的话,有必要用些特别的手段,来提高其实时控制的能力。

1)    语言最好选择C语言,之所以选择C语言,因为工业控制主要基于window API编程,使用很多window内核的服务。
2)    多任务一般采用多线程来实现,根据任务的紧要程度,设置线程的优先级别,一定要设置优先级。
3)    如果在一个线程中有多个任务的话,建议采用协程的方式实现多任务,任务内部最好不要调用Sleep()等休眠函数
4)    为了提高内核的时钟的调度精度,必须使用多媒体定时器并且将其精度设置为1ms,这样内核任务调度精度也提高为1ms,
这估计是微软的内部耦合。如果不使用多媒体定时器的话,内核定时器时钟精度一般只有15ms左右。
5)    每个线程的一个循环必须释放一次CPU,采用Sleep(1),这样才能保证机器的CPU不被耗光,
否则低优先级的任务就没有机会得到执行了。
6)计时采用QueryPerformanceFrequency()和 QueryPerformanceCounter()函数来计时,可以达到ns级别。
上面手段都是用户空间的手段,在内核空间应该也有手段(应用程序作为驱动跑),但是我不熟,就不说了。
3 效果
使用以上几种方式实现的控制程序,时间关键优先级别多线程任务调度精度可以达到2-5ms级别,
计时精度可以达到ns级别。这对于很多(大部分)要求不高的控制来说,基本是够用的。
至于驱动部分,window的驱动的反应速度还是挺快的(CPU占用率不过高的情况下面达到us级别的反应),
这个和我们用户空间的程序不一样,例如window的串口驱动,可以将接收FIFO设置为1,
采用事件方式读串口,即使波特率为115200bps,也不会丢失数据。
使用上面的方法,我在PC机上实现了modbus-RTU客户端,其3.5T设置为5ms,可以很稳定地和PLC主设备通讯。

4 硬实时
如果在PC机上追求真正的硬实时,那么一般是在window或者linux系统中加入硬实时内核,形成双内核系统,
例如RTX或者RTAI,这些都有成功的案例,例如西门子的基于RTX的数控系统,基于RTAI的实时控制系统,
这些实时系统都是用于100us级别的硬实时系统中,需要开发者自己写驱动,其架构是实时内核上跑实时任务,
普通内核上跑一般任务,实时任务和一般任务间通过通讯的方式沟通。由于我还没有在这些实时系统上做过开发,所以暂时不写。


(二)多媒体定时器


在工业生产控制系统中,有许多需要定时完成的操作,如数据采集程序。Win32提供了一个基于消息机制的定时器,使用SetTimer函数创建一个内存对象,设定间隔时间,当到达要求的间隔时,计时器对象发送一个WM_TIMER消息,由相应函数处理。但是由于WM_TIMER优先级低,只有等待消息队列中的其他消息都处理完毕后系统才会响应该消息。而且消息队列中的多个WM_TIMER会被合并,因此Win32定时器的精度低,不能满足工业实时控制系统的要求。


本文将介绍一种精度较高的多媒体定时器,该定时器并不依赖于消息机制,可以实现1ms的定时精度。由于多媒体定时器另外开辟一个独立线程执行定时器回调函数,因此当回调函数耗时较多时并不会导致UI的“假死”,相对而言,Win32定时器隶属于主线程,一旦定时器回调函数耗时较多,就会导致UI的“假死”。


几个拉跨的定时器精度

select选择模型:15ms

Sleep(1) :15ms

timeGetTime: 5ms

QueryPerformanceCounter:<1微秒 (win2000支持)


精度达到1ms的定时器:多媒体定时器(win95支持)

原理分析:多媒体定时器被创建后,应用程序有一个定时器消息队列,系统以1ms(最高精度)的周期向程序发送定时器消息。当应用程序收到消息时,调用回调函数。此回调函数优先级非常高(最高),甚至可以将Sleep的精度压缩到1ms(通过屏蔽Sleep的方法)(普通线程为15ms,即便是将优先级拉到最高)。当回调函数的执行时间大于1ms (内部有如Sleep(1000))或者长耗时循环操作,会导致定时器消息积压,当挤压消失,系统会在1ms内多次(最高速度)调用回调函数 t<1ms,直到定时器消息队列为空,然后恢复1ms周期。


此定时器C++演示代码见:https://blog.csdn.net/u010839382/article/details/52238255


下面勇哥提供C#的演示代码:

using System;
using System.Runtime.InteropServices;

namespace HighPrecisionTimer
{
    /// <summary>
    /// 定时器分辨率:毫秒
    /// </summary>
    struct TimerCaps
    {
        /// <summary>最小周期</summary>
        public int periodMin;
        /// <summary>最大周期</summary>
        public int periodMax;
    }

    /// <summary>
    /// 高精度定时器
    /// </summary>
    public class HPTimer
    {
        static HPTimer()
        {
            TimeGetDevCaps(ref _caps, Marshal.SizeOf(_caps));
        }

        public HPTimer()
        {
            Running = false;
            _interval = _caps.periodMin;
            _resolution = _caps.periodMin;
            _callback = new TimerCallback(TimerEventCallback);
        }

        ~HPTimer()
        {
            TimeKillEvent(_id);
        }

        /// <summary>
        /// 系统定时器回调
        /// </summary>
        /// <param name="id">定时器编号</param>
        /// <param name="msg">预留,不使用</param>
        /// <param name="user">用户实例数据</param>
        /// <param name="param1">预留,不使用</param>
        /// <param name="param2">预留,不使用</param>
        private delegate void TimerCallback(int id, int msg, int user, int param1, int param2);

        #region 动态库接口

        /// <summary>
        /// 查询设备支持的定时器分辨率
        /// </summary>
        /// <param name="ptc">定时器分辨率结构体指针</param>
        /// <param name="cbtc">定时器分辨率结构体大小</param>
        /// <returns></returns>
        [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps")]
        private static extern TimerError TimeGetDevCaps(ref TimerCaps ptc, int cbtc);

        /// <summary>
        /// 绑定定时器事件
        /// </summary>
        /// <param name="delay">延时:毫秒</param>
        /// <param name="resolution">分辨率</param>
        /// <param name="callback">回调接口</param>
        /// <param name="user">用户提供的回调数据</param>
        /// <param name="eventType"></param>
        [DllImport("winmm.dll", EntryPoint = "timeSetEvent")]
        private static extern int TimeSetEvent(int delay, int resolution, TimerCallback callback, int user, int eventType);

        /// <summary>
        /// 终止定时器
        /// </summary>
        /// <param name="id">定时器编号</param>
        [DllImport("winmm.dll", EntryPoint = "timeKillEvent")]
        private static extern TimerError TimeKillEvent(int id);

        #endregion

        #region 属性

        /// <summary>时间间隔:毫秒</summary>
        public int Interval
        {
            get { return _interval; }
            set
            {
                if (value < _caps.periodMin || value > _caps.periodMax)
                    throw new Exception("无效的计时间隔");
                _interval = value;
            }
        }

        public bool Running { get; private set; }

        #endregion

        #region 事件

        public event Action Ticked;

        #endregion

        #region 公开方法

        public void Start()
        {
            if (!Running)
            {
                _id = TimeSetEvent(_interval, _resolution, _callback, 0,
                    (int)EventType01.TIME_PERIODIC | (int)EventType02.TIME_KILL_SYNCHRONOUS);
                if (_id == 0) throw new Exception("启动定时器失败");
                Running = true;
            }
        }

        public void Stop()
        {
            if (Running)
            {
                TimeKillEvent(_id);
                Running = false;
            }
        }

        #endregion

        #region 内部方法

        private void TimerEventCallback(int id, int msg, int user, int param1, int param2)
        {
            Ticked?.Invoke();
        }

        #endregion

        #region 字段

        // 系统定时器分辨率
        private static TimerCaps _caps;
        // 定时器间隔
        private int _interval;
        // 定时器分辨率
        private int _resolution;
        // 定时器回调
        private TimerCallback _callback;
        // 定时器编号
        private int _id;

        #endregion
    }
}


调用方法如下:

static readonly HPTimer timer = new HPTimer();
// 每1毫秒执行一次
timer.Interval =1;
timer.Ticked += Timer_Ticked;
timer.Start();


勇哥注:

把此多媒体定时器程序编制成一个控制台程序,隐藏窗体。然后把进程的级别提高到最高级别“实时”

这样可以实现实时精度为1ms的动作,可以用于IO扫描任务。

这样无论视觉程序有没有卡顿、cpu占用率是不是极高,都不会影响到此定时器的回调周期。

所以在工业设备软件里,最好不要把IO扫描放到主程序里用线程来执行,而是应该按勇哥所说的放在另一个独立的进程里单独执行,并且要保证其有一定精度的实时性,这样就不受其它进程的卡顿的影响。


(三)其它的实时性提升办法


简单的说:

一是编制windows驱动程序(内核级别的应用),这个比用户空间的程序响应快几个数量级,可以达到us,但是这个勇哥实在没那个能力。

二是锁定物理内存,这样可以避免windows使用虚拟内存进行页面交换带来的效率下降,此方面的资料百度很少。


下面引用一下相关的论文,如果作者认为侵权请联系勇哥删除。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!



本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
  • 评论列表:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864