亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

意外的任務取消行為

意外的任務取消行為

C#
哆啦的時光機 2022-11-21 15:46:34
我創建了一個簡單的 .NET Framework 4.7.2 WPF 應用程序,其中包含兩個控件 - 一個文本框和一個按鈕。這是我的代碼:private async void StartTest_Click(object sender, RoutedEventArgs e){    Output.Clear();    var cancellationTokenSource = new CancellationTokenSource();    // Fire and forget    Task.Run(async () => {        try        {            await Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token);        }        catch (OperationCanceledException)        {            Task.Delay(TimeSpan.FromSeconds(3)).Wait();            Print("Task delay has been cancelled.");        }    });    await Task.Delay(TimeSpan.FromSeconds(1));    await Task.Run(() =>    {        Print("Before cancellation.");        cancellationTokenSource.Cancel();        Print("After cancellation.");    });}private void Print(string message){    var threadId = Thread.CurrentThread.ManagedThreadId;    var time = DateTime.Now.ToString("HH:mm:ss.ffff");    Dispatcher.Invoke(() =>    {        Output.AppendText($"{ time } [{ threadId }] { message }\n");    });}按下按鈕后,我在文本框中StartTest看到以下結果:Output12:05:54.1508 [7] Before cancellation.12:05:57.2431 [7] Task delay has been cancelled.12:05:57.2440 [7] After cancellation.我的問題是為什么[7] Task delay has been cancelled.在請求令牌取消的同一線程中執行?我希望看到的是[7] Before cancellation.然后是[7] After cancellation.然后Task delay has been cancelled.?;蛘咧辽賂ask delay has been cancelled.在另一個線程中執行。請注意,如果我cancellationTokenSource.Cancel()從主線程執行,那么輸出看起來像預期的那樣:12:06:59.5583 [1] Before cancellation.12:06:59.5603 [1] After cancellation.12:07:02.5998 [5] Task delay has been cancelled.
查看完整描述

1 回答

?
Qyouu

TA貢獻1786條經驗 獲得超11個贊

我的問題是為什么[7] Task delay has been cancelled.在請求令牌取消的同一線程中執行?

這是因為用flagawait安排它的任務延續ExecuteSynchronously。我也認為這種行為令人驚訝,并且最初將其報告為一個錯誤(作為“按設計”關閉)。

更具體地說,await捕獲一個上下文,如果該上下文與正在完成任務的當前上下文兼容,則async延續將直接在完成該任務的線程上執行。

要逐步完成它:

  • 某些線程池線程“7”運行cancellationTokenSource.Cancel()。

  • 這會導致CancellationTokenSource進入取消狀態并運行其回調。

  • 其中一個回調是Task.Delay. 該回調不是特定于線程的,因此它在線程 7 上執行。

  • 這會導致Task返回的 fromTask.Delay被取消。已經從一個線程池線程安排了它的awaitcontinuation,線程池線程都被認為是相互兼容的,所以asynccontinuation直接在線程7上執行。

提醒一下,線程池線程僅在有代碼要運行時使用。當您使用awaitto發送異步代碼時Task.Run,它可以在一個線程上運行第一部分(直到await),然后在另一個線程上運行另一部分(在 之后await)。

因此,由于線程池線程是可互換的,因此線程 7 在;之后繼續執行該async方法并不是“錯誤的”。await這只是一個問題,因為現在 之后的代碼在那個延續Cancel上被阻塞了。async

請注意,如果我從主線程執行 cancellationTokenSource.Cancel() 那么輸出看起來像預期的那樣

這是因為 UI 上下文被認為與線程池上下文不兼容。因此,當Task返回的 fromTask.Delay被取消時,await將看到它在 UI 上下文中而不是線程池上下文中,因此它將其繼續排隊到線程池而不是直接執行它。

有趣的是,當我替換Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token)cancellationTokenSource.Token.ThrowIfCancellationRequested().NET 時,后臺線程一直處于忙碌狀態,并且輸出再次符合預期

這不是因為線程“忙”。這是因為沒有回調了。所以觀察方法是輪詢而不是被通知。

該代碼設置一個計時器(通過Task.Delay),然后將線程返回到線程池。當定時器計時結束后,從線程池中抓取一個線程,檢查取消令牌源是否被取消;如果不是,則設置另一個計時器并將線程再次返回到線程池。本段的要點是,Task.Run它不僅僅代表“一個線程”;它在執行代碼時只有一個線程(即不在 an 中await),并且線程可以在任何await.


除非您混合使用阻塞代碼和異步代碼,否則await使用的一般問題通常不是問題。ExecuteSynchronously在那種情況下,最好的解決方案是將阻塞代碼更改為異步代碼。如果你不能這樣做,那么你需要小心如何繼續你async在 之后阻塞的方法await。這主要是TaskCompletionSource<T>和的問題CancellationTokenSource。TaskCompletionSource<T>有一個很好的RunContinuationsAsynchronously選項可以覆蓋ExecuteSynchronously標志;不幸的是,CancellationTokenSource沒有;你必須使用排隊你Cancel對線程池的調用Task.Run

獎勵:為您的隊友準備的測驗。


查看完整回答
反對 回復 2022-11-21
  • 1 回答
  • 0 關注
  • 113 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號