【问题标题】:CancellationTokenSource.Cancel() hangsCancellationTokenSource.Cancel() 挂起
【发布时间】:2018-09-21 20:11:30
【问题描述】:

当其中一个异步处于活动循环中时,我观察到 CancellationTokenSource.Cancel 挂起。

完整代码:

static async Task doStuff(CancellationToken token)
{
    try
    {
        // await Task.Yield();
        await Task.Delay(-1, token);
    }
    catch (TaskCanceledException)
    {
    }

    while (true) ;
}

static void Main(string[] args)
{
    var main = Task.Run(() =>
    {
        using (var csource = new CancellationTokenSource())
        {
            var task = doStuff(csource.Token);
            Console.WriteLine("Spawned");
            csource.Cancel();
            Console.WriteLine("Cancelled");
        }
    });
    main.GetAwaiter().GetResult();
}

打印Spawned 并挂起。调用堆栈看起来像:

ConsoleApp9.exe!ConsoleApp9.Program.doStuff(System.Threading.CancellationToken token) Line 23   C#
[Resuming Async Method] 
[External Code] 
ConsoleApp9.exe!ConsoleApp9.Program.Main.AnonymousMethod__1_0() Line 34 C#
[External Code] 

取消await Task.Yield 将导致Spawned\nCancelled 输出。

任何想法为什么? C# 是否保证一次生成的异步永远不会阻塞其他异步?

【问题讨论】:

  • re:你的注释代码,await Task.Delay(TimeSpan.MaxValue, token) 抛出一个参数异常,所以永远不会碰到你的while (true),所以你不会坚持下去。 Timeout.InfiniteTimeSpan 将是您意图的适当论据。
  • >> 你的注释代码,await Task.Delay(TimeSpan.MaxValue, token) 抛出一个参数异常,所以永远不会碰到你的 while (true) 权利
  • >> 从哪里开始;可以从指向文档开始,据说CancellationTokenSource.Cancel可以切换上下文

标签: c# .net async-await cancellationtokensource


【解决方案1】:

CancellationTokenSource 没有任何任务调度程序的概念。如果回调未注册到自定义同步上下文,CancellationTokenSource 将在与.Cancel() 相同的调用堆栈中执行它。在您的情况下,取消回调完成Task.Delay 返回的任务,然后内联延续,导致CancellationTokenSource.Cancel 内部出现无限循环。

您的 Task.Yield 示例仅适用于竞争条件。当令牌被取消时,线程还没有开始执行Task.Delay,因此没有继续内联。如果您更改 Main 以添加暂停,您会发现即使使用 Task.Yield,它仍会冻结:

static void Main(string[] args)
{
    var main = Task.Run(() =>
    {
        using (var csource = new CancellationTokenSource())
        {
            var task = doStuff(csource.Token);
            Console.WriteLine("Spawned");

            Thread.Sleep(1000); // Give enough time to reach Task.Delay

            csource.Cancel();
            Console.WriteLine("Cancelled");
        }
    });
    main.GetAwaiter().GetResult();
}

目前,保护对CancellationTokenSource.Cancel 的调用的唯一可靠方法是将其包装在Task.Run 中。

【讨论】:

  • 谢谢,还没有听说过继续内联选项。奇怪,CancellationTokenSource.Cancel 没有像 ConfigureAwait 这样的额外接口
  • @nyaapa 好吧,我提交了至少有一种方法可以在没有任何内联的情况下取消令牌:github.com/dotnet/corefx/issues/23716 但它没有得到太大的牵引力
  • 我刚才遇到了这个问题,用CancelAfter(0)解决了,我不知道取消是什么以及为什么要等待,就是这样,将IsCancellationRequested设置为true并继续,为什么一定要挂吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多