创建一个可等待的 System.Timers.Timer

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)

更新:我更新了示例代码和预期输出,使用了接受答案的执行的实际输出。

Ste*_*ary 5

似乎我必须编写一个返回 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事件。并且Elapsedawait完成之前删除该事件处理程序。因此,从事件触发到下一次等待计时器时,没有处理程序,您的消费代码很容易错过一些事件。

如果这是不可接受的,那么您应该使用IObservable<T>,它是围绕订阅然后接收事件模型设计的,或者使用类似 Channels 的东西来缓冲事件并使用异步流使用它们。