在.Net 4.0中使用差的mans async / await构造实现异步超时

时间:2012-07-02 20:45:16

标签: c# timeout pattern-matching task-parallel-library async-await

动机

C#5.0异步/等待构造非常棒,但遗憾的是,微软只展示了.NET 4.5和VS 2012的候选版本,这些技术将在我们的项目中得到广泛采用需要一些时间。

在Stephen Toub的Asynchronous methods, C# iterators, and Tasks我找到了一个可以很好地用于.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中使用CancellationTokenSource时,有一个构造函数将时间跨度作为超时参数,因此生成的CancellationTokenSource会在指定的时间段内取消。
.Net 4.0无法超时,那么在.Net 4.0中这样做的正确方法是什么?

3 个答案:

答案 0 :(得分:5)

FWIW,您可以在4.0项目中使用async / await,只需使用the async targeting pack即可。适合我的作品!

答案 1 :(得分:4)

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

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

修改

我上面的初步回答是基本的想法,但是在Is CancellationTokenSource.CancelAfter() leaky?(实际上是你正在寻求的构造函数的.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;
}

答案 2 :(得分:0)

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