異步等待功能使得編寫非阻塞代碼更加優雅。但是,雖然非阻塞,但在異步函數內執行的工作仍然是不平凡的。何時同時運行異步功能?
在編寫異步代碼時,我發現編寫遵循模式「沿着兔子洞一路向下」的代碼是很自然的,可以這麼說,調用樹中的所有方法都標記爲異步,並且使用的API是異步;但即使在非阻塞的情況下,執行的代碼也會佔用相當多的上下文線程時間。
如何以及何時異步運行同步異步方法?是否應該在調用樹中創建更高或更低的新任務時出錯?有沒有這種「優化」的最佳做法?
異步等待功能使得編寫非阻塞代碼更加優雅。但是,雖然非阻塞,但在異步函數內執行的工作仍然是不平凡的。何時同時運行異步功能?
在編寫異步代碼時,我發現編寫遵循模式「沿着兔子洞一路向下」的代碼是很自然的,可以這麼說,調用樹中的所有方法都標記爲異步,並且使用的API是異步;但即使在非阻塞的情況下,執行的代碼也會佔用相當多的上下文線程時間。
如何以及何時異步運行同步異步方法?是否應該在調用樹中創建更高或更低的新任務時出錯?有沒有這種「優化」的最佳做法?
我一直在使用async
生產了幾年。我推薦幾個核心「最佳實踐」:
async
code。 「一路下來」使用async
。 (推論:更喜歡async Task
到async void
除非你有使用async void
)。ConfigureAwait(false)
。您已經計算出「async
一路下降」的部分,並且您正處於ConfigureAwait(false)
變得有用的程度。
假設您有一個async
方法A
,該方法調用另一個async
方法B
。 A
用B
的結果更新UI,但B
不依賴於UI。因此,我們有:
async Task A()
{
var result = await B();
myUIElement.Text = result;
}
async Task<string> B()
{
var rawString = await SomeOtherStuff();
var result = DoProcessingOnRawString(rawString);
return result;
}
在這個例子中,我會叫B
一個「庫」方法,因爲它並不真正需要在UI上下文中運行。現在,B
確實在UI線程中運行,因此DoProcessingOnRawString
正在引起響應問題。
所以,在B
添加ConfigureAwait(false)
每await
:
async Task<string> B()
{
var rawString = await SomeOtherStuff().ConfigureAwait(false);
var result = DoProcessingOnRawString(rawString);
return result;
}
現在,當後B
簡歷await
荷蘭國際集團SomeOtherStuff
(假設它實際上確實有await
),它會恢復一個線程池線程,而不是的UI上下文。當B
完成時,即使它正在線程池上運行,A
將在UI上下文中恢復。
您不能將ConfigureAwait(false)
添加到A
,因爲A
取決於UI上下文。
您還可以選擇將任務明確排隊到線程池(await Task.Run(..)
),如果您有特定的CPU密集型功能,則您可以應該。但是,如果您的表現受到「成千上萬的剪紙」的影響,您可以使用ConfigureAwait(false)
將大量的「家務」卸載到線程池中。
你可能會發現我的intro post helpful(它進入更多的「爲什麼」),而async
FAQ也有很多很好的參考。
異步等待does not actually use threads in the current .NET process-space。它專爲阻塞IO和網絡操作而設計,如數據庫調用,Web請求,某些文件IO。
我無法感知C#中的優勢是什麼,您稱之爲兔洞技術。這樣做只會掩蓋代碼,不必要地將潛在的高CPU代碼耦合到您的IO代碼。
直接回答你的問題,我只會用異步等待對上述IO /網絡方案,就在那裏,你正在做的阻塞操作點,以及任何被CPU綁定,我會用穿線技術來充分利用可用的CPU內核。沒有必要混合這兩個問題。
非常感謝你,這是一個頂尖的答案,真的有助於澄清這個問題! –
「幾年」? – spender
異步CTP,回來的路......我覺得在這一點上差不多2年。我一直在做十幾年的異步編程(http://nitoprograms.blogspot.com/2012/09/an-async-horror-story.html),所以我是一個早期的早期採用者'async'。 –