Реализация асинхронного тайм-аута с использованием асинхронных / ожидающих конструкций бедного человека в.Net 4.0

мотивация

C# 5.0 асинхронные / ожидающие конструкции - это круто, но, к сожалению, Microsoft показала только кандидат на выпуск.NET 4.5 и VS 2012, и потребуется некоторое время, чтобы эти технологии получили широкое распространение в наших проектах.

В асинхронных методах Стефана Тауба , итераторах C# и Задачах я нашел замену, которую можно красиво использовать в.NET 4.0. Есть также дюжина других реализаций, которые делают возможным использование этого подхода даже в.NET 2.0, хотя они кажутся немного устаревшими и менее функциональными.

пример

Итак, теперь мой код.NET 4.0 выглядит так (прокомментированные разделы показывают, как это делается в.NET 4.5):

//private async Task ProcessMessageAsync()
private IEnumerable<Task> ProcessMessageAsync()
{
    //var udpReceiveResult = await udpClient.ReceiveAsync();

    var task = Task<UdpAsyncReceiveResult>
               .Factory
               .FromAsync(udpClient.BeginReceive, udpClient.EndReceive, null);

    yield return task;

    var udpReceiveResult = task.Result;

    //... blah blah blah

    if (message is BootstrapRequest)
    {
        var typedMessage = ((BootstrapRequest)(message));

        // !!! .NET 4.0 has no overload for CancellationTokenSource that 
        // !!! takes timeout parameter :(
        var cts 
          = new CancellationTokenSource(BootstrapResponseTimeout); // Error here

        //... blah blah blah

        // Say(messageIPEndPoint, responseMessage, cts.Token);

        Task.Factory.Iterate(Say(messageIPEndPoint, responseMessage, cts.Token));
    }
}

Выглядит немного некрасиво, хотя это делает работу

Вопрос

При использовании CancellationTokenSource в.NET 4.5 существует конструктор, который принимает временной интервал в качестве параметра времени ожидания, так что в результате CancellationTokenSource отменяет в течение указанного периода времени.
.Net 4.0 не может тайм-аут, так как правильно сделать это в.Net 4.0?

3 ответа

Решение

Это действительно имеет отношение к async/await? Похоже, вам просто нужен способ отменить токен, независимо от async / await, верно? В этом случае, вы могли бы просто создать таймер, который вызывает Cancel после истечения времени ожидания?

new Timer(state => cts.Cancel(), null, BootstrapResponseTimeout, Timeout.Infinite);

РЕДАКТИРОВАТЬ

Мой первоначальный ответ, приведенный выше, является основной идеей, но более надежное решение можно найти в разделе "Утечки" CancellationTokenSource.CancelAfter()? (на самом деле.Net 4.5 реализация конструктора, который вы ищете). Вот функция, которую вы можете использовать для создания токенов тайм-аута на основе этого кода.

public static CancellationTokenSource CreateTimeoutToken(int dueTime) {
    if (dueTime < -1) {
        throw new ArgumentOutOfRangeException("dueTime");
    }
    var source = new CancellationTokenSource();
    var timer = new Timer(self => {
        ((Timer)self).Dispose();
        try {
            source.Cancel();
        } catch (ObjectDisposedException) {}
    });
    timer.Change(dueTime, -1);
    return source;
}

FWIW, вы можете использовать async/await в проектах 4.0, просто используйте пакет таргетинга async. Прекрасно работает для меня!

Вы все еще можете использовать CancelAfter(), который является методом расширения в Microsoft.Bcl.Async что очень похоже на принятый ответ выше.

Это код источника ссылки, когда я нажимаю F12, чтобы увидеть реализацию CancelAfter():

  /// <summary>Cancels the <see cref="T:System.Threading.CancellationTokenSource" /> after the specified duration.</summary>
  /// <param name="source">The CancellationTokenSource.</param>
  /// <param name="dueTime">The due time in milliseconds for the source to be canceled.</param>
  public static void CancelAfter(this CancellationTokenSource source, int dueTime)
  {
    if (source == null)
      throw new NullReferenceException();
    if (dueTime < -1)
      throw new ArgumentOutOfRangeException("dueTime");
    Contract.EndContractBlock();
    Timer timer = (Timer) null;
    timer = new Timer((TimerCallback) (state =>
    {
      timer.Dispose();
      TimerManager.Remove(timer);
      try
      {
        source.Cancel();
      }
      catch (ObjectDisposedException ex)
      {
      }
    }), (object) null, -1, -1);
    TimerManager.Add(timer);
    timer.Change(dueTime, -1);
  }
Другие вопросы по тегам