2

人为的例子,但假设我在异步方法中有以下内容:

var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
cts.Token.Register(Callback);
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);

只要在几秒钟后调用回调,这就会按预期工作。但是,我想在 之后处理注册Task.Delay,所以假设我进行以下修改:

var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
using (cts.Token.Register(Callback))
{
    SomethingThatMightThrow();
    await Task.Delay(10000, cts.Token);
}

在这种情况下,不会调用Callback ,可能是因为来自 的异常await Task.Delay...导致注册在被调用之前被释放。

确保Callback在取消时调用和始终处理注册的最佳方法是什么?

我确实想到了以下内容,但我不确定它有多强大:

var cts = new CancellationTokenSource();
cts.CancelAfter(2000);

var ctr = cts.Token.Register(Callback);
try
{
    SomethingThatMightThrow();
    await Task.Delay(10000, cts.Token);
}
finally
{
    if (!cts.Token.IsCancellationRequested)
        ctr.Dispose();
}
4

2 回答 2

2

CancellationToken.Register通常用于将新CancellationToken系统与使用其他类型的取消通知的旧系统互操作。它不打算用作通用的“取消回调”。

如果您想在取消操作时以某种方式响应,那么您只需捕获相应的异常:

using (var cts = new CancellationTokenSource())
{
  cts.CancelAfter(2000);
  SomethingThatMightThrow();
  try
  {
    await Task.Delay(10000, cts.Token);
  }
  catch (OperationCanceledException)
  {
    Callback();
  }
}
于 2014-08-29T12:37:12.977 回答
1

与其说该方法可能不会抛出异常,不如说它可能不会像我想要的那样快速抛出它。例如,我注意到一些 Azure SDK 异步方法可能需要很长时间才能响应令牌上发出的取消信号

根据您的评论,您可以选择创建自己的计时器,以根据您的标准明确指定方法何时“运行时间过长”。为此,您可以使用Task.WhenAny

using (var cts = new CancellationTokenSource())
{
    try
    {
        var cancellationDelayTask = Task.Delay(2000, cts.Token);
        var taskThatMightThrow = SomethingThatMightThrowAsync(cts.Token);

        if ((await Task.WhenAny(taskThatMightThrow, cancellationDelayTask)) 
             == cancellationDelayTask)
        {
            // Task.Delay "timeout" finished first.
        }
    }
    catch (OperationCanceledException)
    {
        Callback();
    }
}
于 2014-08-30T08:53:20.837 回答