Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
227 views
in Technique[技术] by (71.8m points)

c# - Creating an awaitable System.Timers.Timer

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It seems that I must write an extension method that returns a TaskAwaiter, but I am not sure what to do exactly.

The easiest way to return an awaiter is to get a Task and then call GetAwaiter 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>:

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

So, that will make your sample code work as expected. However, there is a significant caveat: each await will call GetAwaiter, which subscribes to the next Elapsed event. And that Elapsed event handler is removed before the await 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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...