簡體   English   中英

CancellationTokenSource.Cancel()掛起

[英]CancellationTokenSource.Cancel() hangs

當異步之一處於活動循環中時,我觀察到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#是否保證曾經屈服的異步永遠不會阻止其他異步?

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM