Создание ожидаемого System.Timers.Timer - PullRequest
0 голосов
/ 02 июля 2019

У меня была идея создать таймер, который можно ожидать, вместо того, чтобы вызывать события. Я еще не думал о каких-либо практических приложениях, и, возможно, это не будет чем-то ужасно полезным, но я хотел бы посмотреть, возможно ли это как упражнение. Вот как это можно использовать:

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

Таймер ожидается 10 раз, и ожидаемый результат:

Ожидается 0, время сигнала: 06: 08: 51.674
Ожидается 1, время сигнала: 06: 08: 51.783
Ожидается 2, время сигнала: 06: 08: 51,891
Ожидается 3, время сигнала: 06: 08: 52.002
Ожидается 4, время сигнала: 06: 08: 52.110
Ожидается 5, время сигнала: 06: 08: 52.218
Ожидается 6, время сигнала: 06: 08: 52.332
Ожидается 7, время сигнала: 06: 08: 52.438
Ожидается 8, время сигнала: 06: 08: 52,546
Ожидается 9, время сигнала: 06: 08: 52.660

В этом случае простой await Task.Delay(100) будет делать то же самое, но таймер дает гибкость управления интервалом из другой части программы (с учетом возможных проблем безопасности потока ).

Что касается реализации, я обнаружил статью , в которой описывается, как сделать различные вещи ожидаемыми, например TimeSpan, int, DateTimeOffset и Process. Кажется, что я должен написать метод расширения, который возвращает TaskAwaiter, но я не уверен, что именно делать. У кого-нибудь есть идеи?

public static TaskAwaiter GetAwaiter(this System.Timers.Timer timer)
{
    // TODO
}

Обновление: Я обновил пример кода и ожидаемый результат, используя фактический результат выполнения принятого ответа .

1 Ответ

3 голосов
/ 02 июля 2019

Кажется, я должен написать метод расширения, который возвращает TaskAwaiter, но я не уверен, что именно делать.

Самый простой способ вернуть ожидающего - это получитьTask, а затем назовите 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();
    }
}

Итак, ваш пример кода будет работать так, как ожидается.Однако есть существенное предупреждение: каждый await будет вызывать GetAwaiter, который подписывается на следующее событие Elapsed.И этот обработчик Elapsed удаляется до завершения await.Таким образом, с момента запуска события до следующего ожидания таймера существует обработчик no , и ваш потребляющий код может легко пропустить некоторые события.

Если это не приемлемо, тогдавы должны использовать IObservable<T>, который разработан на основе модели подписка-затем-получение-событий, или использовать что-то вроде каналов для буферизации событий и использования их в асинхронном потоке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...