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

18

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();

它可以编译(显然是个好的起点),但我不确定后果。计时器会在重置计时器之前等待回调吗?

4个回答

30

计时器会在重置之前等待回调函数吗?

不会。它没有任何可以等待的东西,因为ElapsedEventHandler的签名具有void返回类型。

换句话说,你的代码等价于:

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

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

这是否可接受,取决于您的上下文。一般来说,使用异步void方法或匿名函数会使它们更难测试和重用——但正是出于事件处理程序的考虑才赋予了这种能力...您应该考虑错误将如何传播。


15

问题的标题专门涉及计时器,但如果我们将其视为“如何在一段时间后调用异步方法?”,那么您可以在不使用计时器的情况下完成。

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
}
请注意,这将具有不同的定时方式(计时器将以间隔调用,上面的代码将在每次(运行时间+睡眠时间)被调用,但即使MyMethod2花费很长时间,它也不会被调用两次。话虽如此,您可以计算等待多长时间以运行“每x分钟”。


1
在Azure上减缓Web请求时使用了这个。每秒超过10个会导致错误。谢谢。 - Eric Wild
听起来不错。我会尝试在服务中测试它。我通常使用计时器,但这次我必须进行几个连续的异步调用。它可能有效!非常感谢你! - David Con

-2
@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
}

tareas.Remove(finishedTask);tareas 变量未定义。 - Theodor Zoulias

-2

实际上,你可以。

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

就算价值有限,尝试这样做还是导致我的整个程序崩溃了。 - Shadow

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