I had an idea about creating a timer that can be awaited, instead of raising events. I haven't thought of any practical applications yet, and may not be something terribly useful, but I would like to see if it's at least doable as an exercise. This is how it could be used:
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}");
}
The timer is awaited 10 times, and the expected output is:
Awaited 0, SignalTime: 06:08:51.674
Awaited 1, SignalTime: 06:08:51.783
Awaited 2, SignalTime: 06:08:51.891
Awaited 3, SignalTime: 06:08:52.002
Awaited 4, SignalTime: 06:08:52.110
Awaited 5, SignalTime: 06:08:52.218
Awaited 6, SignalTime: 06:08:52.332
Awaited 7, SignalTime: 06:08:52.438
Awaited 8, SignalTime: 06:08:52.546
Awaited 9, SignalTime: 06:08:52.660
In this case a simple await Task.Delay(100)
would do the same thing, but a timer gives the flexibility of controlling the interval from another part of the program (with the caveat of possible thread safety issues).
Regarding the implementation, I found an article that describes how to make various things awaitable, like a TimeSpan
, an int
, a DateTimeOffset
and a Process
. It seems that I must write an extension method that returns a TaskAwaiter
, but I am not sure what to do exactly. Does anyone has any idea?
public static TaskAwaiter GetAwaiter(this System.Timers.Timer timer)
{
// TODO
}
Update: I updated the example code and the expected output, using actual output from the execution of the accepted answer.
The easiest way to return an awaiter is to get a
Task
and then callGetAwaiter
on it. You can also create custom awaiters, but that's much more involved.So the question becomes "how do I get a task that is completed when an event is raised?" And the answer to that is to use
TaskCompletionSource<T>
:So, that will make your sample code work as expected. However, there is a significant caveat: each
await
will callGetAwaiter
, which subscribes to the nextElapsed
event. And thatElapsed
event handler is removed before theawait
completes. So from the time the event fires until the next time the timer is awaited, there is no handler, and your consuming code can easily miss some events.If this is not acceptable, then you should use
IObservable<T>
, which is designed around a subscription-then-receive-events model, or use something like Channels to buffer the events and consume them with an asynchronous stream.