日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁

發(fā)布時(shí)間:2023/12/4 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

我在?使用 Task.Wait()?立刻死鎖(deadlock)?一文中站在類庫使用者的角度看?async/await?代碼的死鎖問題;而本文將站在類庫設(shè)計(jì)者的角度來看死鎖問題。

閱讀本文,我們將知道如何編寫類庫代碼,來盡可能避免類庫使用者出現(xiàn)那篇博客中描述的死鎖問題。



現(xiàn)在,我們是類庫設(shè)計(jì)者的身份,我們?cè)噲D編寫一個(gè)?RunAsync?方法用以異步執(zhí)行某些操作。

private async Task RunAsync()
{
// 某些異步操作。
}

類庫的使用者可能多種多樣,一個(gè)比較有素養(yǎng)的使用者會(huì)考慮這樣使用類庫:

放心,這樣的類庫使用者是不會(huì)出什么岔子的。

然而,這世間既然有讓人省心的類庫使用者,當(dāng)然也存在非常讓人不省心的類庫使用者。當(dāng)你的類庫遍布全球,你真的會(huì)遇到這樣的使用者:

或者高級(jí)一些,使用?AutoResetEvent?和?try/finally?塊的使用者:

// 這段代碼如果在 foo.RunAsync() 第一次調(diào)用返回之前再調(diào)用一次,則可能死鎖。
_autoResetEvent.WaitOne();
try
{
await foo.RunAsync();
}
finally
{
_autoResetEvent.Set();
}

如果這段代碼在 UI 線程執(zhí)行,那么極有可能出現(xiàn)死鎖,就是我在?使用 Task.Wait()?立刻死鎖(deadlock)?一文中說的那種死鎖,詳情可進(jìn)去看原因。

那么現(xiàn)在做一個(gè)調(diào)查,你認(rèn)為下面三種?RunAsync?的實(shí)現(xiàn)中,哪些會(huì)在碰到這種不省心的類庫使用者時(shí)發(fā)生死鎖呢?

答案是——

第 2 種!

只有第 2 種會(huì)發(fā)生死鎖,第 1 和第 3 種都不會(huì)。


對(duì)于第 2 種情況,下方“await?之后的代碼”試圖回到 UI 線程執(zhí)行,但 UI 此時(shí)處于調(diào)用者?foo.RunAsync().Wait();?這段神奇代碼的等待狀態(tài)——所以死鎖了。回到 UI 線程靠的是?DispatcherSynchronizationContext,我在?使用 Task.Wait()?立刻死鎖(deadlock)?一文中已有解釋,建議前往了解更深層次的原因。

private async Task RunAsync1()
{
await Task.Run(() =>
{
// 某些異步操作。
});
// await 之后的代碼(即使沒寫任何代碼,也是需要執(zhí)行的)。
}

那為什么第 1 種和第 3 種不會(huì)死鎖呢?

對(duì)第 1 種情況,由于并沒有寫?async/await,所以異步狀態(tài)機(jī)?AsyncMethodStateMachine?此時(shí)并不執(zhí)行。直接返回了?Task,這相當(dāng)于此時(shí)創(chuàng)建的?Task?對(duì)象直接被調(diào)用者的?foo.RunAsync().Wait();?神奇代碼等待了。也就是說,等待的?Task?是真正執(zhí)行異步任務(wù)的?Task。

Task?的?Wait()?方法內(nèi)部通過自旋鎖來實(shí)現(xiàn)等待,可以閱讀?.NET 中的輕量級(jí)線程安全 - walterlv?了解自旋鎖,也可以前往 .NET Framework 源碼?Task.SpinWait?了解?Task.SpinWait()?方法的具體實(shí)現(xiàn)。

//spin only once if we are running on a single CPU
int spinCount = PlatformHelper.IsSingleProcessor
? 1
: System.Threading.SpinWait.YIELD_THRESHOLD;
for (int i = 0; i < spinCount; i++)
{
if (IsCompleted)
{
return true;
}

if (i == spinCount / 2)
{
Thread.Yield();
}
else
{
Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i));
}
}

當(dāng)?Run?中的異步任務(wù)結(jié)束后,自旋鎖即發(fā)現(xiàn)任務(wù)結(jié)束?Task.IsCompleted?為?True,于是等待結(jié)束,不會(huì)發(fā)生死鎖。

對(duì)第 3 種情況,由于指定了?ConfigureAwait(false),這意味著通知異步狀態(tài)機(jī)?AsyncMethodStateMachine?并不需要使用設(shè)置好的?SynchronizationContext(對(duì)于 UI 線程,是?DispatcherSynchronizationContext)執(zhí)行線程同步,而是使用默認(rèn)的?SynchronizationContext,而默認(rèn)行為是隨便找個(gè)線程執(zhí)行后面的代碼。于是,await Task.Run?后面的代碼便不需要返回原線程,也就不會(huì)發(fā)生第 2 種情況里的死鎖問題。


建議安裝 NuGet 包?Microsoft.CodeAnalysis.FxCopAnalyzers。這樣,當(dāng)你在代碼中寫出?await?時(shí),分析器會(huì)提示你?CA2007?警告,你必須顯式設(shè)置?ConfigureAwait(false)?或?ConfigureAwait(true)?來提醒你是否需要使用默認(rèn)的?SynchronizationContext。

如果你是類庫的編寫者,注意此問題能夠一定程度上防止逗比使用者出現(xiàn)死鎖問題后噴你的類庫寫得不好。


死鎖問題:

  • 使用 Task.Wait()?立刻死鎖(deadlock) - walterlv

  • 不要使用 Dispatcher.Invoke,因?yàn)樗赡茉谀愕难舆t初始化?Lazy<T>?中導(dǎo)致死鎖 - walterlv

  • 在有 UI 線程參與的同步鎖(如 AutoResetEvent)內(nèi)部使用 await 可能導(dǎo)致死鎖

  • .NET 中小心嵌套等待的 Task,它可能會(huì)耗盡你線程池的現(xiàn)有資源,出現(xiàn)類似死鎖的情況 - walterlv

解決方法:

  • 在編寫異步方法時(shí),使用 ConfigureAwait(false) 避免使用者死鎖 - walterlv

  • 將 async/await 異步代碼轉(zhuǎn)換為安全的不會(huì)死鎖的同步代碼(使用 PushFrame) - walterlv

原文地址:https://blog.walterlv.com/post/using-configure-await-to-avoid-deadlocks.html

.NET社區(qū)新聞,深度好文,歡迎訪問公眾號(hào)文章匯總?http://www.csharpkit.com?


總結(jié)

以上是生活随笔為你收集整理的在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。