編輯
我發現
uj5u.com熱心網友回復:
您已經有幾種解決方案。我只是想多描述一下這個問題。這里有幾個因素在起作用,共同導致觀察到的重入。
一是lock重入。lock嚴格來說是執行緒互斥,與代碼互斥不同。我認為在 99% 的情況下(如我的博客所述),重入鎖是一個壞主意,因為開發人員通常希望代碼互斥而不是執行緒。SemaphoreSlim,因為它不是可重入的,所以相互排斥代碼。IMO 重入鎖是幾十年前的遺留物,當時它們是作為作業系統概念引入的,而作業系統只關心管理執行緒。
接下來,TaskCompletionSource<T>默認情況下同步呼叫任務延續。
此外,await將其方法延續安排為同步任務延續(如我的博客所述)。
即使安排了同步,任務延續有時也會異步運行,但在這種情況下它們將同步運行。捕獲的背景關系await是執行緒池背景關系,完成執行緒(呼叫者TCS.TrySet*)是執行緒池執行緒,在這種情況下,延續幾乎總是同步運行。
因此,您最終會得到一個獲取鎖的執行緒,完成一個 TCS,從而執行該任務的延續,其中包括繼續另一個方法,然后該方法能夠獲取相同的鎖。
要在其他答案中重復現有解決方案,要解決此問題,您需要在某個時候打破該鏈:
- (OK) 使用不可重入鎖。
SemaphoreSlim.WaitAsync在持有鎖的同時仍會執行延續(不是一個好主意),但由于SemaphoreSlim不是可重入的,方法延續將(異步)等待鎖可用。 - (最佳)使用
TaskCompletionSource.RunContinuationsAsynchronously,這將強制任務繼續到(不同的)執行緒池執行緒。這是一個更好的解決方案,因為您的代碼在持有鎖時不再呼叫任意代碼(即任務繼續)。
您還可以await通過對 TCS的方法使用非執行緒池背景關系來中斷鏈。例如,如果該方法必須在 UI 執行緒上恢復,則它不能從執行緒池執行緒同步運行。
從更廣泛的角度來看,如果您混合使用鎖和TaskCompletionSource實體,聽起來您可能正在構建(或可能需要)異步協調原語。如果有幫助的話,我有一個開源庫可以實作其中的一堆。
uj5u.com熱心網友回復:
任務是對一定量作業的抽象。通常這意味著作業被分成幾部分,執行可以在部分之間暫停和恢復。恢復時它很可能在另一個執行緒上運行。但是暫停/恢復只能在await陳述句中完成。值得注意的是,當任務“暫停”時,例如因為它正在等待 IO,它根本不消耗任何執行緒,它只會在實際運行時使用一個執行緒。
我的問題是:設計合理嗎?這是否意味著異步方法中的鎖是沒有意義的?
異步方法中的鎖遠非毫無意義,因為它允許您確保一段代碼一次只能由一個執行緒運行。
在您的第一個示例中,一次只能有一個執行緒擁有鎖。當鎖被持有時,該任務不能被暫停/恢復,因為await在鎖體中是不合法的。因此,單個執行緒將執行整個鎖體,并且該執行緒在完成鎖體之前不能做任何其他事情。因此,除非您呼叫一些可以回呼相同方法的代碼,否則不會有重新進入的風險。
在您更新的示例中,問題的發生是由于TaskCompletionSource.SetException,允許重用當前執行緒以立即運行任務的任何延續。為避免這種情況以及許多其他問題,請確保僅在運行有限數量的代碼時持有鎖。任何可能運行任意代碼的方法呼叫都有導致死鎖、重入和許多其他問題的風險。
您可以通過使用 ManualResetEvent(Slim) 在執行緒之間執行信號而不是使用 TaskCompletionSource 來解決特定問題。
uj5u.com熱心網友回復:
所以你的方法基本上是這樣的:
static void MethodNotAllowReetrance()
{
lock (methodLock) tcs.SetResult();
}
...并且 thetcs.Task附加了一個呼叫MethodNotAllowReetrance. 如果你的方法是這樣的,那么會發生同樣的事情:
static void MethodNotAllowReetrance()
{
lock (methodLock) MethodNotAllowReetrance();
}
道德教訓是,每次呼叫受lock保護區域內的任何方法時都必須非常小心。在這種特殊情況下,您有幾個選擇:
TaskCompletionSource握住 時不要完成lock。將其完成推遲到您退出受保護區域之后:
static void MethodNotAllowReetrance()
{
bool doComplete = false;
lock (methodLock) doComplete = true;
if (doComplete) tcs.SetResult();
}
TaskCompletionSource通過TaskCreationOptions.RunContinuationsAsynchronously在其建構式中傳遞 來配置,使其異步呼叫其延續。這是您不經常使用的選項。例如,當您取消 a 時CancellationTokenSource,您沒有選擇異步呼叫注冊到其關聯的回呼的選項CancellationToken。MethodNotAllowReetrance以可以處理重入的方式重構該方法。
uj5u.com熱心網友回復:
使用SemaphoreSlim代替lock, 因為,正如檔案所說:
SemaphoreSlim 類不強制執行執行緒或任務標識
在您的情況下,它看起來像這樣:
// Semaphore only allows one request to enter at a time
private static readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
void SyncMethod() {
_semaphoreSlim.Wait();
try {
// Do some sync work
} finally {
_semaphoreSlim.Release();
}
}
該try/finally塊是可選的,但它可以確保即使一個例外在你的代碼拋出的某處信號被釋放。
注意SemaphoreSlim還有一個WaitAsync()方法,如果要異步等待進入信號量。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/407570.html
標籤:
上一篇:基于承諾的計數器
