1 回答

TA貢獻1786條經(jīng)驗 獲得超11個贊
我的問題是為什么
[7] Task delay has been cancelled.
在請求令牌取消的同一線程中執(zhí)行?
這是因為用flagawait
安排它的任務(wù)延續(xù)ExecuteSynchronously
。我也認(rèn)為這種行為令人驚訝,并且最初將其報告為一個錯誤(作為“按設(shè)計”關(guān)閉)。
更具體地說,await
捕獲一個上下文,如果該上下文與正在完成任務(wù)的當(dāng)前上下文兼容,則async
延續(xù)將直接在完成該任務(wù)的線程上執(zhí)行。
要逐步完成它:
某些線程池線程“7”運行
cancellationTokenSource.Cancel()
。這會導(dǎo)致
CancellationTokenSource
進入取消狀態(tài)并運行其回調(diào)。其中一個回調(diào)是
Task.Delay
. 該回調(diào)不是特定于線程的,因此它在線程 7 上執(zhí)行。這會導(dǎo)致
Task
返回的 fromTask.Delay
被取消。已經(jīng)從一個線程池線程安排了它的await
continuation,線程池線程都被認(rèn)為是相互兼容的,所以async
continuation直接在線程7上執(zhí)行。
提醒一下,線程池線程僅在有代碼要運行時使用。當(dāng)您使用await
to發(fā)送異步代碼時Task.Run
,它可以在一個線程上運行第一部分(直到await
),然后在另一個線程上運行另一部分(在 之后await
)。
因此,由于線程池線程是可互換的,因此線程 7 在;之后繼續(xù)執(zhí)行該async
方法并不是“錯誤的”。await
這只是一個問題,因為現(xiàn)在 之后的代碼在那個延續(xù)Cancel
上被阻塞了。async
請注意,如果我從主線程執(zhí)行 cancellationTokenSource.Cancel() 那么輸出看起來像預(yù)期的那樣
這是因為 UI 上下文被認(rèn)為與線程池上下文不兼容。因此,當(dāng)Task
返回的 fromTask.Delay
被取消時,await
將看到它在 UI 上下文中而不是線程池上下文中,因此它將其繼續(xù)排隊到線程池而不是直接執(zhí)行它。
有趣的是,當(dāng)我替換
Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token)
為cancellationTokenSource.Token.ThrowIfCancellationRequested()
.NET 時,后臺線程一直處于忙碌狀態(tài),并且輸出再次符合預(yù)期
這不是因為線程“忙”。這是因為沒有回調(diào)了。所以觀察方法是輪詢而不是被通知。
該代碼設(shè)置一個計時器(通過Task.Delay
),然后將線程返回到線程池。當(dāng)定時器計時結(jié)束后,從線程池中抓取一個線程,檢查取消令牌源是否被取消;如果不是,則設(shè)置另一個計時器并將線程再次返回到線程池。本段的要點是,Task.Run
它不僅僅代表“一個線程”;它在執(zhí)行代碼時只有一個線程(即不在 an 中await
),并且線程可以在任何await
.
除非您混合使用阻塞代碼和異步代碼,否則await
使用的一般問題通常不是問題。ExecuteSynchronously
在那種情況下,最好的解決方案是將阻塞代碼更改為異步代碼。如果你不能這樣做,那么你需要小心如何繼續(xù)你async
在 之后阻塞的方法await
。這主要是TaskCompletionSource<T>
和的問題CancellationTokenSource
。TaskCompletionSource<T>
有一個很好的RunContinuationsAsynchronously
選項可以覆蓋ExecuteSynchronously
標(biāo)志;不幸的是,CancellationTokenSource
沒有;你必須使用排隊你Cancel
對線程池的調(diào)用Task.Run
。
獎勵:為您的隊友準(zhǔn)備的測驗。
- 1 回答
- 0 關(guān)注
- 111 瀏覽
添加回答
舉報