【问题标题】:Disposing CancellationTokenRegistrations处理 CancellationTokenRegistration
【发布时间】:2014-08-29 11:10:28
【问题描述】:

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

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();
}

【问题讨论】:

  • 不清楚您是否总是希望/期望Callback 被调用,或者代码中是否有某个点之后您将不再需要回调即使 CancelAfter 时间到了也会被调用。如果您总是希望调用回调(即使您的其余代码已完成),只需在 Callback 中处理 CancellationTokenRegistration
  • @MattSmith - 我希望只有在 SomethingThatMightThrow(); await Task.Delay(10000, cts.Token); 块期间请求取消时才调用 Callback
  • Task.Delay 和 Token.Register 都在令牌上注册回调。 Task.Delay 的回调首先被调用(这会导致 await 完成并且 using 语句完成并且 CancellationTokenRegistration 被释放)。因此,当它到达下一个回调时,它已经被取消注册了。
  • @MattSmith - 是的,这就是我想象的事情,这就是我的问题的真正目的 - 鉴于情况如此,因此我提供的第二个代码 sn-p 不起作用(因为@ 987654333@ 没有被调用),最好的选择是什么?

标签: c# async-await idisposable using cancellationtokensource


【解决方案1】:

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

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

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

【讨论】:

  • 谢谢,这很有帮助。然而,我对这种方法的担忧是,我现在依赖于我在 try 块中等待的方法,及时抛出 OperationCanceledException (Task.Delay 表现良好,我敢肯定,但在我的实际代码我正在等待其他东西) - 我的想法是通过在令牌上注册一个回调,而不是保证它会在取消请求后很快被调用。
  • @Duncan:如果一个方法采用CancellationToken,它应该抛出一个OperationCanceledException
  • 该方法可能不会抛出异常,因为它可能不会像我想要的那样快速抛出它。例如,我注意到一些 Azure SDK 异步方法可能需要很长时间才能响应令牌上发出的取消信号...
  • 如果您确定令牌被取消,那么您可以Register(不释放)并在取消后使用Task.WhenAll返回.
  • @Duncan 另一种方法是创建一个Task,在您希望它超时的时间段后超时,并将Task.WhenAny 与天蓝色请求和您的超时一起使用,并检查它是否是超时任务还是先完成的 azure sdk。
【解决方案2】:

该方法可能不会抛出异常,因为 它可能不会像我想要的那样快速扔掉它。我注意到一些 例如,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();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多