【问题标题】:does the timer thread wait till all the steps in the callback function are done or does the callback function get reinvoked in every period计时器线程是等到回调函数中的所有步骤完成还是在每个周期重新调用回调函数
【发布时间】:2013-08-01 13:58:42
【问题描述】:

我有一个定时器线程函数

SampleTimer = new Timer(SomeTask,0,70000)

回调函数如下

void SomeTask(object o)
{
//block using autoresetevent
}

问题是 SomeTask() 回调方法每 70 秒调用一次,即使回调方法中的所有操作仍未完成。 如何防止计时器在其中的所有步骤完成之前调用 SomeTask() 函数

【问题讨论】:

  • "计时器" - 我知道框架中有 3 个类/组件,称为 Timer,它们可能都有不同的行为。您能否确定您指的是哪一个(例如命名空间)?
  • 那么当定时器再次触发时你该怎么办,忽略它?还是排队等待以后执行?
  • 嗯..如果问题是多次调用,为什么不只是 Sleep() 循环它。微不足道,不可能并发运行。为什么似乎每个人都在不合适的时候使用计时器?
  • @MartinJames:因为计时器几乎总是比使用Sleep 绑定线程更可取。在这种情况下,计时器 是合适的,并且防止并发滴答声很简单。带有Sleep 循环的线程通常表示设计不佳。见Programs are not cats
  • @JimMischel - 这是一个未准备好的线程,所以它是死代码。谁在乎,即使在托管应用程序中?在可能出现不必要的多次调用时使用计时器是设计不当的标志。如果您使用 Sleep() 循环,防止并发滴答声并不简单 - 它是固有的,不是问题。

标签: c# multithreading timer


【解决方案1】:

我假设您使用的是System.Threading.Timer

一种方法是一次性创建计时器,然后在线程完成其任务后重新启动它。这样您就可以确定不会有任何重叠:

myTimer = new Timer(someMethod, null, 70000, Timeout.Infinite);

在你的回调中:

void TimerCallback(object o)
{
    // do stuff here
    // then change the timer
    myTimer.Change(70000, Timeout.Infinite);
}

为周期指定 Timeout.Infinite 会禁用周期信号,将计时器变为一次性。

另一种方法是使用监视器:

object TimerLock = new object();

void TimerCallback(object o)
{
    if (!Monitor.TryEnter(TimerLock))
    {
        // already in timer. Exit.
        return;
    }

    // do stuff

    // then release the lock
    Monitor.Exit(TimerLock);
}

如果您想知道为什么我不使用 try/finally 作为锁,请参阅 Eric Lippert 的博客 Locks and exceptions do not mix

这两种方法的主要区别在于,在第一种方法中,计时器将在前一个回调执行完成后 70 秒触发。在一秒钟内,计时器将在 70 秒的周期内触发,因此下一个回调可能会在前一个回调完成后的任何时间执行,从一秒后到 70 秒后。

对于我所做的大多数事情,我展示的第一个技术似乎效果更好。

【讨论】:

  • 以前从未见过Monitor。整洁。
  • 你应该在 finally 块中写上“myTimer.Change(70000, Timeout.Infinite)” 以便下次出现异常时运行。
  • @SumitJoshi 你确定吗?如果您的计时器回调抛出异常,您确定您的程序仍处于一致状态吗?在第一个抛出异常之后,你真的想要另一个回调吗?你可以这样做,但这总是一个好主意。
【解决方案2】:

并发调用是可能的,这是 .NET 和 Windows 计时器的一个非常烦人的属性。您必须自己确保互斥,也许只是使用锁(不需要事件)。但是,这给您带来了另一个问题,因为如果计时器计时太快,则任意数量的线程可能会在锁前排队。所以最好把锁TryEnter锁进去,进不去就什么都不做。那样你会丢失一个刻度,但由于无论如何不能保证刻度,你的应用程序必须能够处理它。

在使用计时器时,几乎无法保证滴答的时间、计数和并发性。

【讨论】:

    【解决方案3】:

    我相信object o 被触发的计时器。所以你也许可以这样做:

    var myTimer = object as Timer;
    

    (但我会检查一下...)

    无论您的SomeTask 花费多长时间,它都会在您的时间间隔内触发。

    我总是这样做

    private System.Timers.Timer _timer;
    
    public static void main()
    {
        _timer = SampleTimer = new Timer(SomeTask,0,70000);
    }
    
    void SomeTask(object o)
    {
        _timer.Enabled = false;
        try{
        // ...  work...
        }finally{
            _timer.Enabled = true;
        }
    }
    

    更新:带锁更安全。

    private static object _padLock = new object();
    
    void SomeTask(object o)
    {
        // lock around disabling the timer
        lock(_padLock)
        {
            // return if some other thread beat us to it.
            if (!_timer.Enabled) return;
            _timer.Enabled = false;
        }
    
        try{
        // ...  work...
        }finally{
            _timer.Enabled = true;
        }
    }
    

    为了超级安全。如果您只想运行一个“SomeTask”实例。而且,您不希望他们排队。您应该在 SomeTask 中放置一个 lock()。您可以跳过禁用计时器。

    【讨论】:

    • 这个技巧不会阻止并发修改,因为任意数量的线程可以同时到达_timer.Enabled = false。实际上,您正在同时使用 Timer 实例,这不是线程安全的。
    • 问题不在于多个计时器触发同一个委托。问题是关于一项长期运行的任务。使用锁可以使其更安全,但这取决于 OP 的目标。也许不同计时器的多次触发 OK。在这种情况下,他们应该将对象转换为 Timer 并禁用/启用它。
    • 同一个计时器可以同时触发给定的委托。这实际上是在 OPs 案例中发生的情况。此代码仅在您假设“善意”线程调度时才有效。
    • Enabled = true 调用仍然可以与 Enabled = false 并发发生 :)
    • 锁保护_timer.Enabled = false。如果另一个线程碰巧在_timer 被禁用之前进入SomeTask,它将等待锁定然后返回,因为if(!_timer.Enabled) return; OP 正在处理一个定时器。所以这解决了这个问题。如果有多个计时器,那么这是一个不同的问题,因为他们可能希望允许同时执行不同的计时器。在这种情况下,我需要实际使用object o 作为禁用/启用的计时器。并且可能每个都有不同的锁。但锁应该足够快,无所谓。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多