【问题标题】:CancellationToken blocks thread executionCancellationToken 阻止线程执行
【发布时间】:2021-05-05 17:24:41
【问题描述】:

我正在尝试使用取消令牌从另一个线程中跳出循环:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace CancellationTokenTest
{
    class Program
    {
        private static CancellationTokenSource token;

        static async Task Main(string[] args)
        {
            token = new CancellationTokenSource();
            var t1 = LongProcess1();

            for (int i = 0; i < 5; i++)
            {
                try
                {
                    await Task.Delay(2000, token.Token);
                    Console.WriteLine($"Main awaited {i}");
                }
                catch (OperationCanceledException)
                {
                    break;
                }
            }

            Console.WriteLine("Main loop completed");
            t1.Wait();
            Console.WriteLine("Application end");
        }

        static async Task LongProcess1()
        {
            for (int i = 0; i < 5; i++)
            {
                await Task.Delay(1000);
                Console.WriteLine($"LongProcess1 awaited {i}");
            }

            Console.WriteLine("Submitting cancel");
            token.Cancel();     // <------ Blocking execution
            Console.WriteLine("Cancellation done!");
        }
    }
}

问题是Console.WriteLine("Cancellation done!"); 行永远不会被执行,Main() 方法一直在等待t1 完成。

我不明白为什么!

我在 .NET 5 上运行它,但它似乎并不依赖于框架。 有人可以帮助我了解发生了什么吗?

【问题讨论】:

  • 删除t1.Wait();。这导致了僵局。当Cancel() 被调用时,线程进入回到Main 中的for 循环 - 但随后该线程被t1.Wait() 阻塞,这将阻止它继续进入LongProcess1 .
  • 我注意到您的程序不是多线程的 - 这就是它遇到死锁的原因。最好的解决方案不是开始使用线程,而是继续使用单线程,消除老式的阻塞操作。
  • 如果我删除了t1.Wait()“取消完成!”发生在“应用程序结束”之后(我认为不可能......)。在 Cacel 之后添加延迟会导致“取消完成!”永远不会发生。有没有办法让它在“应用程序结束”之前发生?
  • @Dai .... 你开发过一个单一的多线程应用程序吗?
  • @MartinJames 我承认当我写到“程序不是多线程的”这在技术上是不正确的(事实上,斯蒂芬的回答说,它使用线程池)。我的意思是,OP 的程序 在任何时间点都只会有一个运行用户代码的线程 - 并不是说​​在他们的进程的用户代码中永远存在一个线程 -还是您有不同的看法?

标签: c# multithreading async-await task-parallel-library cancellation-token


【解决方案1】:

这个死锁几乎和described on my blog 一样。您需要注意的一件事是,在这种情况下,CancellationTokenSource.Cancel 正在调用 Cancel 的线程上运行取消令牌延续

所以,这就是发生的事情:

  • Main 开始 LongProcess
  • LongProcess 使用一些 awaits 并在线程池线程上恢复。
  • 与此同时,Main 正在执行await Task.Delay
  • LongProcess 执行Cancel 时,它实际上会在当前线程上继续执行Main 方法。您可以通过在 catch 块中放置断点并查看调用堆栈来验证这一点。
  • 当前线程(线程池线程)然后阻塞任务 (.Wait())。
  • 现在,LongProcess 无法完成,因为当前运行它的线程被阻塞等待它完成。

要修复它,请将Wait() 更改为await

【讨论】:

  • 谢谢,这已经解决了。我很高兴在你的博客上看到这个!
【解决方案2】:

问题是t1.Wait();Wait()几乎总是问题;p) - 这是在窃取线程。改用await t1;,一切都会奏效。实际上,cancellation 需要调用一些回调(处于取消状态的延续),然后返回到取消的任何位置,并且您在取消中插入了阻塞。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    相关资源
    最近更新 更多