【问题标题】:Implementing async timeout using poor mans async/await constructs in .Net 4.0在 .Net 4.0 中使用糟糕的 mans async/await 结构实现异步超时
【发布时间】:2026-01-05 05:05:03
【问题描述】:

动机

C# 5.0 async/await 结构非常棒,但不幸的是,微软只展示了 .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 中这样做的正确方法是什么?

【问题讨论】:

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


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    这真的与 async/await 有什么关系吗?看起来你只需要一种方法来取消令牌,独立于异步/等待,对吧?在这种情况下,您是否可以简单地创建一个在超时后调用 Cancel 的 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;
    }
    

    【讨论】:

    • 你建议使用什么样的计时器?
    • System.Threading.Timer 是我的想法,但我想它们中的任何一个都应该工作——我不是各种.Net Timers 之间差异的专家。
    • 它不应该阻止任何东西;你在想 Thread.Sleep() 吗?每次触发定时器时,定时器都会从线程池中抓取一个新线程并在其上运行。开销应该非常低。
    • 如果它们真的是同时的,您应该能够在所有这些调用中重用相同的 cts 令牌,对吧?即使没有,线程所做的只是调用 cts.Cancel() 并退出——因此,除非您以微秒的间隔调用它们,否则一次不太可能需要多个线程。
    • @Lu4 实际上你可以看到 .Net 4.5 CancellationTokenSource.CancelAfter 方法的代码,这里是由TimeSpan 构造函数调用的:*.com/questions/10715688/…。与上述基本思想相同;应该正是您所需要的。
    【解决方案3】:

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

    【讨论】: