2012-09-18 158 views
1

異步等待功能使得編寫非阻塞代碼更加優雅。但是,雖然非阻塞,但在異步函數內執行的工作仍然是不平凡的。何時同時運行異步功能?

在編寫異步代碼時,我發現編寫遵循模式「沿着兔子洞一路向下」的代碼是很自然的,可以這麼說,調用樹中的所有方法都標記爲異步,並且使用的API是異步;但即使在非阻塞的情況下,執行的代碼也會佔用相當多的上下文線程時間。

如何以及何時異步運行同步異步方法?是否應該在調用樹中創建更高或更低的新任務時出錯?有沒有這種「優化」的最佳做法?

回答

6

我一直在使用async生產了幾年。我推薦幾個核心「最佳實踐」:

  1. Don't block on async code。 「一路下來」使用async。 (推論:更喜歡async Taskasync void除非你使用async void)。
  2. 儘可能在您的「庫」方法中使用ConfigureAwait(false)

您已經計算出「async一路下降」的部分,並且您正處於ConfigureAwait(false)變得有用的程度。

假設您有一個async方法A,該方法調用另一個async方法BAB的結果更新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也有很多很好的參考。

+0

非常感謝你,這是一個頂尖的答案,真的有助於澄清這個問題! –

+0

「幾年」? – spender

+0

異步CTP,回來的路......我覺得在這一點上差不多2年。我一直在做十幾年的異步編程(http://nitoprograms.blogspot.com/2012/09/an-async-horror-story.html),所以我是一個早期的早期採用者'async'。 –

0

異步等待does not actually use threads in the current .NET process-space。它專爲阻塞IO和網絡操作而設計,如數據庫調用,Web請求,某些文件IO。

我無法感知C#中的優勢是什麼,您稱之爲兔洞技術。這樣做只會掩蓋代碼,不必要地將潛在的高CPU代碼耦合到您的IO代碼。

直接回答你的問題,我只會用異步等待對上述IO /網絡方案,就在那裏,你正在做的阻塞操作點,以及任何被CPU綁定,我會用穿線技術來充分利用可用的CPU內核。沒有必要混合這兩個問題。