The*_*ias -2 .net c# timer async-await
我有一个关于创建一个可以等待的计时器的想法,而不是引发事件。我还没有想到任何实际应用,可能不是非常有用的东西,但我想看看它是否至少可以作为练习。这是它的用法:
var timer = new System.Timers.Timer();
timer.Interval = 100;
timer.Enabled = true;
for (int i = 0; i < 10; i++)
{
var signalTime = await timer;
Console.WriteLine($"Awaited {i}, SignalTime: {signalTime:HH:mm:ss.fff}");
}
Run Code Online (Sandbox Code Playgroud)
计时器等待 10 次,预期输出为:
等待 0,信号时间:06:08:51.674
等待 1,信号时间:06:08:51.783
等待 2,信号时间:06:08:51.891
等待 3,信号时间:06:08:52.002
等待 4,信号时间:05:108:
等待 5、信号时间:06:08:52.218
等待 6、信号时间:06:08:52.332
等待 7、信号时间:06:08:52.438
等待 8、信号时间:06:08:52.546
等待 06:52.332 信号时间:06:08:52.0608:
在这种情况下,一个简单的await Task.Delay(100)
会做同样的事情,但计时器提供了从程序的另一部分控制间隔的灵活性(注意可能的线程安全问题)。
关于实现,我找到了一篇文章,描述了如何使各种事物成为可等待的,例如 a TimeSpan
、 an int
、 aDateTimeOffset
和 a Process
。似乎我必须编写一个返回 a 的扩展方法TaskAwaiter
,但我不确定该怎么做。有没有人有任何想法?
public static TaskAwaiter GetAwaiter(this System.Timers.Timer timer)
{
// TODO
}
Run Code Online (Sandbox Code Playgroud)
更新:我更新了示例代码和预期输出,使用了接受答案的执行的实际输出。
似乎我必须编写一个返回 TaskAwaiter 的扩展方法,但我不确定要做什么。
返回 awaiter 的最简单方法是获取 aTask
然后调用GetAwaiter
它。您还可以创建自定义等待者,但这涉及更多。
所以问题变成了“我如何获得在引发事件时完成的任务?” 答案是使用TaskCompletionSource<T>
:
public static class TimerExtensions
{
public static Task<DateTime> NextEventAsync(this Timer timer)
{
var tcs = new TaskCompletionSource<DateTime>();
ElapsedEventHandler handler = null;
handler = (_, e) =>
{
timer.Elapsed -= handler;
tcs.TrySetResult(e.SignalTime);
};
timer.Elapsed += handler;
return tcs.Task;
}
public static TaskAwaiter<DateTime> GetAwaiter(this Timer timer)
{
return timer.NextEventAsync().GetAwaiter();
}
}
Run Code Online (Sandbox Code Playgroud)
因此,这将使您的示例代码按预期工作。但是,有一个重要的警告:每个人await
都会调用GetAwaiter
,它订阅下一个Elapsed
事件。并且Elapsed
在await
完成之前删除该事件处理程序。因此,从事件触发到下一次等待计时器时,没有处理程序,您的消费代码很容易错过一些事件。
如果这是不可接受的,那么您应该使用IObservable<T>
,它是围绕订阅然后接收事件模型设计的,或者使用类似 Channels 的东西来缓冲事件并使用异步流使用它们。