在 .Net 4.0 中使用穷人的异步/等待构造实现异步超时



动机

C# 5.0 async/await 构造非常棒,不幸的是,Microsoft只显示了 .NET 4.5 和 VS 2012 的候选版本,这些技术需要一些时间才能在我们的项目中得到广泛采用。

在 Stephen Toub 的异步方法、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));
    }
}

看起来有点丑,虽然它能完成工作

问题

在 .NET 4.5 中使用 CancelTokenSource 时,有一个构造函数将 timespan 作为超时参数,以便生成的CancellationTokenSource在指定的时间段内取消。
.Net 4.0 无法超时,那么在 .Net 4.0 中执行此操作的正确方法是什么?

FWIW,你可以在 4.0 项目中使用 async/await,只需使用 async 目标包即可。 对我有用!

这真的与 async/await 有什么关系吗? 看起来您只需要一种方法来取消令牌,独立于异步/等待,对吧? 在这种情况下,您可以简单地创建一个在超时后调用取消的计时器吗?

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

编辑

我上面的初步反应是基本思想,但更强大的解决方案可以在 CancelTokenSource.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;
}

您仍然可以使用 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);
  }

相关内容

  • 没有找到相关文章

最新更新