【问题标题】:Calling Method only once from a Threading.Timer从 Threading.Timer 只调用一次方法
【发布时间】:2014-06-07 10:52:47
【问题描述】:

我有一个 System.Threading.Timer 频繁触发(为了简单起见,我们说每秒一次),在 CallBack 中我需要调用一个 Action(通过构造函数传入,因此位于另一个类中),我在其中做一些处理(假设需要 2 秒以上),我将如何防止我的处理逻辑被多次调用?似乎 lock() 在 Action 调用中不起作用? 我使用的是 .net 3.5。

public TestOperation(Action callBackMethod)
{
    this.timer = new System.Threading.Timer(timer_Elapsed, callbackMethod, timerInterval, Timeout.Infinite);
}

private void timer_Elapsed(object state)
{
    Action callback = (Action) state;
    if (callback != null)
    {
        callback();
    }
}

// example of the callback, in another class. 
private void callBackMethod()
{
    // How can I stop this from running every 1 second? Lock() doesn't seem to work here
    Thread.Sleep(2000);
}

谢谢!

【问题讨论】:

  • 使用一个任务并让该任务在完成后安排一个新任务。如果需要,您可以将第二个任务延迟一秒。
  • 创建一次性计时器并在回调中更新。见stackoverflow.com/a/684452/56778

标签: c# multithreading .net-3.5


【解决方案1】:

您可以使用布尔标志来防止重入:

    bool executing;

    public TestOperation(Action callBackMethod)
    {
        this.timer = new System.Threading.Timer(timer_Elapsed, callbackMethod, timerInterval, Timeout.Infinite);
    }

    private void timer_Elapsed(object state)
    {
        if(executing)
             return;


        Action callback = (Action) state;
        if (callback != null)
        {
            executing = true;
            callback();
        }

    }

    // example of the callback, in another class. 
    private void callBackMethod()
    {
        // How can I stop this from running every 1 second? Lock() doesn't seem to work here

        Thread.Sleep(2000);
        executing = false;

    }

【讨论】:

  • 如果您有锁,则不需要布尔标志。 executing 标志仅在持有锁时被修改。此外,lock(this) 是非常糟糕的做法。我强烈建议任何看到此答案的人按照建议行事。这根本不是一个好的解决方案。
  • 有线交叉,是的,必须删除锁(如果使用锁并且计时器是线程计时器,即使它被锁定也会引发事件,并且会排队很多aclls),还有,一个问题@JimMischel,你为什么说使用 lock(this) 是一种不好的做法?如果您只在受控部分执行此操作,则可以确保您永远不会将 lock() 调用到 null 对象,对此有什么真正的担忧吗?
【解决方案2】:

你可以这样做并完全避免使用计时器。

void Main()
{
    RunPeriodicAsync();
}
async Task RunPeriodicAsync()
{
    while(true)
    {
        await Task.Delay(someTimeSpan);
        DoTheThing();
        if(!goAgain)break;
    }

}

或者如果您需要支持取消:

void Main()
{
    var cts=new CancellationTokenSource();
    RunPeriodicAsync(cts.Token);
    //sometime later
    cts.Cancel();
}
async Task RunPeriodicAsync(CancellationToken ct)
{
    while(!ct.IsCancellationRequested)
    {
        await Task.Delay(1000);
        DoTheWork();
    }
}

没有 async/await 你可以:

System.Threading.Timer timer;
void Main()
{
    RunActionAfter(() => DoTheWork(), 2000);
}
void RunActionAfter(Action action, int period)
{
    //Timeout.Infinite means the timer runs only once.
    timer = new Timer(_ => action(), null, 2000, Timeout.Infinite); 
}
void DoTheWork()
{
    Console.WriteLine("!!!");

    //then maybe
    RunActionAfter(() => DoTheWork(),2000);
}

【讨论】:

  • 不幸的是,我正在运行.net 3.5...否则这会简单得多。
【解决方案3】:

解决这个问题没什么好说的。请注意,使用 lock 是一个非常糟糕的主意,当回调持续​​花费太多时间时,它会使您的线程池爆炸。机器加载时很容易发生这种情况。使用 Monitor.TryEnter() 是安全的选择。肯定也不漂亮,你会随意丢失回调。

如果您简单地将 period 参数设置为 0,它会变得更容易。这样计时器只能滴答一次。现在,您自动获得了无法重新输入回调的硬性保证。您所要做的就是在方法结束时调用 Change() 来重新启动计时器。您可以选择使用固定值还是根据实际过期时间计算新的 dueTime 值,这都是合理的选择。

【讨论】:

  • 谢谢,这听起来很简单。我只是想解决这个问题,CallBack 不是异步的吗?那么它不会在调用后立即运行,然后直接调用 timer.Change() - 如果我将它放入 time_Elapsed 方法中)?然后哪个会允许计时器在 CallBack 未完成的情况下再次触发?还是说回调(操作)是异步的我错了?
  • 好吧,当然,回调是异步的。这就是使用定时器的意义所在。您已将 Change() 调用放入回调方法中。在方法的最后,当您执行完需要时间的代码时。
猜你喜欢
  • 2019-09-24
  • 2013-01-04
  • 2019-04-01
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多