在不使用线程池的情况下,使用C# 实现定期任务

3
有没有一种在C#中执行定期任务/线程的方法,而不使用线程池(这是我主要的要求之一)。
预期场景如下:
1. 周期性线程阻塞/等待计时器事件(来自系统)。 2. 系统在计时器到期时通知线程。 3. 线程执行一些代码。 4. 线程阻塞/等待来自系统的下一个计时器事件(请注意,如果线程错过了一个或多个计时器事件,这没关系)。
最接近这一点的是 System.Forms.Timer,但问题在于它的精度仅限于55毫秒,这对我来说不够。
System.Timers.Timer似乎可以通过SynchronizingObject属性处理此问题。然而,我不确定如何根据上述情况实现ISynchronizeInvoke。我找到的所有用于实现ISynchronizeInvoke的代码示例似乎都太复杂,无法处理如此简单的情况。
有没有一种简单实现此功能的方法,如上述场景所示(不使用线程池),并比System.Forms.Timer更准确?

你需要什么精度水平? - John Koerner
我处理的最短时间大约是10毫秒。 - user1258126
你在做什么需要每10毫秒执行一次的操作?也许有更好的解决方案。请给我们一个大局观,告诉我们你想要做什么,以避免 XY 问题 - Scott Chamberlain
主要监控/轮询被动设备。 - user1258126
编辑您的问题,并准确地展示您是如何监控设备的,它使用什么接口,是否有库或者您是通过COM端口进行通信等。请提供更多细节信息,可能曾经使用过相同设备的人可以从他们解决类似问题时所学到的知识中给您一些建议。 - Scott Chamberlain
4个回答

4

Windows计时器默认分辨率为15.6毫秒,来自于他们的文档:

在Windows中,默认的系统定时器分辨率为15.6毫秒,这意味着每隔15.6毫秒,操作系统会从系统定时器硬件接收一个时钟中断。当时钟中断触发时,Windows会更新计时器计数并检查是否已经过期了预定的计时器对象。一些应用程序需要更频繁地服务于计时器到期,而不是每隔15.6毫秒。

他们还指出:

应用程序可以调用timeBeginPeriod来增加定时器分辨率。最大1ms的分辨率用于支持图形动画、音频播放或视频播放。这不仅将应用程序的定时器分辨率增加到1ms,还会影响全局系统定时器分辨率,因为Windows至少使用任何应用程序请求的最高分辨率(即最低间隔)。因此,如果只有一个应用程序请求1ms的定时器分辨率,则系统定时器将把间隔(也称为“系统定时器滴答”)设置为至少1ms。有关更多信息,请参见MSDN®网站上的“timeBeginPeriod函数”。所以,如果你真的需要高分辨率定时器,你可以使用timeBeginPeriod调用来影响全局系统定时器,但要知道它会影响设备的电池寿命/功耗。
此外,还有QueryPerformanceCounterQueryPerformanceFrequency调用可用于在.NET中获得更低级别的精度,就像这个例子中一样:http://www.codeproject.com/Articles/571289/Obtaining-Microsecond-Precision-in-NET。请保留HTML标签。

2

多媒体定时器将为您提供所有定时器中最高的分辨率。如果您确实需要10毫秒或更少的分辨率,那么这将是唯一的选择。

如果您发现不需要多媒体定时器提供的分辨率,那么可等待定时器将使您能够编写一个循环,在单个线程内等待计时器到期。

您也可以选择定时器队列,它提供了大量的计时器,而不会产生其他计时器的资源开销,但默认情况下使用系统线程池。


0

在不重新配置系统计时器的情况下,我的系统上System.Timers.Timer的最快响应时间约为16毫秒。将您的Timer.Interval值设置为(int)(目标间隔/16)*16,然后让线程休眠剩余的时间(如果您确实需要更好的精度,则解释性语言如C#并不是正确的选择):

class Program
{
    private static Timer _timer = new Timer();
    private static DateTime _last = DateTime.Now;
    private const int IntendedInterval = 25;

    static void Main(string[] args)
    {
        _timer.AutoReset = false;
        _timer.Interval = (int)(IntendedInterval / 16) * 16;
        _timer.Elapsed += _timer_Elapsed;
        _timer.Start();

        Console.ReadLine();
    }

    static void _timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        var now = DateTime.Now;
        var duration = now - _last;

        var sleep = IntendedInterval - duration.TotalMilliseconds;

        if (0 < sleep )
        {
            System.Threading.Thread.Sleep(( int )sleep);
        }

        _timer.Start();

        now = DateTime.Now;
        duration = now - _last;
        _last = now;

        Console.WriteLine(duration.TotalMilliseconds.ToString());
    }
}

0
你尝试过简单地让你的线程睡眠吗?
int msecSleepTime = 25;  // number of milliseconds to sleep
Thread.Sleep(msecSleepTime);  // sleep the current thread for the given number of milliseconds

这不符合我的需求。假设我需要每25毫秒运行一次线程,而我的代码执行大约需要10毫秒。这意味着下一次执行代码将在35毫秒后而不是25毫秒后! - user1258126
你可以记录代码执行所需的时间,然后进行计算,找出每次需要睡眠多少毫秒。 - Scott Chamberlain
如果没有其他更好的选择,也许我会采取这种方法。 - user1258126
1
这不是一个坏选择 - 它是自包含的,不使用其他线程来运行计时,并且可以在调用函数中的任何位置工作,而无需诉诸事件驱动状态机。 - Martin James

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接