如何等待异步计时器回调

时间:2019-11-27 20:24:17

标签: c# timer async-await

我正在使用System.Threading.Timer运行一些async

这意味着我的回调为async void

private async void CallbackAsync(object state) {
    _timer.Change(Timeout.Infinite, Timeout.Infinite);
    await SomethingAsync();
    _timer.Change(TimeToNextCallback, Timeout.Inifinite);
}

对于非异步回调,我使用了这样的代码来安全地关闭计时器。

using (var waitHandle = new AutoResetEvent(false)) {
    if (_timer.Dispose(waitHandle))        {
        waitHandle.WaitOne();
    }
}

我的理解是,这不适用于异步回调。回调将在第一个await上返回,并且计时器不知道计划的继续。也就是说,上面的代码可能会在延续完成之前返回(并且回调可能会尝试更改已处置的计时器)。

首先,我的理解正确吗?

第二,等待异步回调完成的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

您的理解是正确的,您的Timer将不会意识到您的CallbackAsync函数仍在运行,因此您对Dispose(WaitHandle)的调用将返回并立即向您的WaitHandle发信号,在处置CallbackAsync之后,到Timer.Change的呼叫可能会呼叫Timer

这里有一些危险信号:

  1. 您正在使用async void。这几乎总是一个坏主意,因为异步函数应该返回一个Task,否则调用者没有任何等待的机会。
  2. 您正在混合使用ThreadTask范例。

由于TimerCallback不支持任务,因此在仍然使用Timer的情况下使其正常工作的唯一方法是通过删除async/await关键字并使用{{ 1}},直到Task.Wait返回的Task完成为止。正如其他人所提到的,通过创建SomethingAsync并将其传递给CancellationToken来添加对取消的支持也是明智的,这样,当您调用SomethingAsync时,您可以取消正在运行的回调(此Dispose需要由CancellationToken传递给它调用的任何其他异步函数,并且/或者在SomethingAsync执行时定期检查是否取消)。

编辑:如果您想转换为基于自然的SomethingAsync的方式,请参见Run async method regularly with specified interval