System.Timers.Timer 的 Elapsed 回调可以是异步的吗?

Can the Elapsed callback of a System.Timers.Timer be async?

是否可以(甚至合理)使 System.Timers.Timer 的回调成为异步方法?类似于:

var timer = new System.Timers.Timer
{
   Interval = TimeSpan.FromSeconds(30).TotalMilliseconds,
   AutoReset = true
};
timer.Elapsed += async (sender, e) => { /* await something */ };
timer.Start();

它编译(显然是一个很好的起点),但我不确定我是否理解后果。定时器会await重置定时器前的回调吗?

Will the timer await the callback before resetting the timer?

没有。 可以 等待什么,因为 ElapsedEventHandler 的签名具有 void return 类型。

换句话说,您的代码相当于:

var timer = new System.Timers.Timer { ... };
timer.Elapsed += Foo;
timer.Start();

...
private async void Foo()
{
    ...
}

您是否可以接受这取决于您的背景。通常,使用异步 void 方法或匿名函数会使它们更难测试和重用 - 但这种能力正是为了事件处理程序而提供的......你应该考虑如何传播错误。

问题的标题专门针对定时器,但如果我们将其视为 "How to call an async method after some time?" 那么您可以在不使用定时器的情况下完成。

var task2 = Task.Run(async () => {
    while (true)
    {
        try
        {
            await MyMethod2();
        } catch
        {
            //super easy error handling
        }
        await Task.Delay(TimeSpan.FromSeconds(5));
    }
});

...

public async Task MyMethod2()
{
    //async work here
}

但是请注意,这会有不同的时间(定时器将每隔一段时间调用一次,上面的代码将每隔(运行 次 + sleep_time)调用一次,但即使 MyMethod2 需要很长时间它不会被调用两次。话虽如此,你可以计算等待多长时间 运行 'every x minutes'.

其实可以。

System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += async (x, y) => { await Task.Delay(1); };

@tymtam 提出的解决方案不会等到 MyMethod2 结束。
我觉得用这个比较好。
一个有两个异步任务的例子,当两个任务都完成后,等待 5 秒并再次执行这两个任务:

var task2 = Task.Run(async () => {
    while (true)
    {
        try
        {
            var task1 = MyMethod1();
            var task2 = MyMethod2();
            List<Task> allTasks = new List<Task> { task1, task2 };
            while (allTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(allTasks);
                if (finishedTask == task1)
                {
                   Console.WriteLine("MyMethod1 has ended");
                }
                else if (finishedTask == task2)
                {
                   Console.WriteLine("MyMethod2 has ended");
                }
                tareas.Remove(finishedTask);
            }
            //Here only when finished all task
        } catch
        {
            //super easy error handling
        }
        //Wait until next cycle
        await Task.Delay(TimeSpan.FromSeconds(5));
    }
});

...

public async Task MyMethod1()
{
    //async work here
}

public async Task MyMethod2()
{
    //async work here
}