2016-09-17 72 views
0

我有一個類並行運行數千個異步任務,遠遠大於工作線程的數量。如果我在等待呼叫時沒有執行.ConfigureAwait(false),那麼性能會降低很多,但將.ConfigureAwait(false)附加到每個等待呼叫都很乏味,並且會降低代碼的可讀性。我正在尋找一種方法來在一個函數中指定某種類型的null上下文,以產生這些任務,以便每個在它們內部等待的調用自動地不關心SynchronizationContext。可能嗎?有沒有辦法將.ConfigureAwait(false)應用於整個任務子樹?

更新:我做了一些谷歌上搜索,它看起來像曾經我已經與.ConfigureAwait(false)一個函數內部當前的SynchronizationContext將是無效和我不需要去關心它的子功能調用。那是對的嗎?

+1

我不認爲這是可能的,而且我覺得用'真'作爲默認的'ConfigureAwait'是一個很大的錯誤,太多。 – dasblinkenlight

+0

@dasblinkenlight,不同意默認值「錯誤」。當你異步調用方法時,「默認情況下」期望繼續將在相同的「上下文」中執行。 – Fabio

+1

@Fabio據我所知,唯一真正重要的地方就是UI。在其他地方,你並不關心它,至少,你並不太在意同意降低性能和死鎖的可能性。 – dasblinkenlight

回答

2

它看起來像一旦我已經在.ConfigureAwait(false)函數內,當前的SynchronizationContext將爲空,我不需要在子函數調用中關心它。那是對的嗎?

好了,實際情況是:

  • 你是一個異步函數中。
  • 您開始一項任務。
  • 在完成任務之前,您對該任務執行了await(配置爲false)。

所以,這不是一個很好的情況依賴。具體來說,如果任務快速完成(在您配置的await被擊中之前),那麼您仍將處於原始上下文中。請注意,即使您不期望,也會發生這種情況;例如,移動設備對緩存Web請求極其積極。

要「走出」同步上下文,只需將其包裝在Task.Run中 - 它非常簡短地使用線程池線程,並執行該線程池上下文中的所有後代碼。

+0

因此,當我在Task.Run中時,我從不需要使用ConfigureAwait? – Poma

+1

@Poma:在'Task.Run'中調用''ConfigureAwait'時不起作用。如果你的代碼**知道**它總是**將被從Task.Run中調用,那麼是的,你可以跳過它。 –

+0

'你在這個任務完成之前等待(配置爲false)。「 - 這是不正確的假設。在調用返回任務的方法後,不能保證任務完成。該任務可以在完整狀態下返回。 –

2

是的,你可以。通過重置當前的SynchronizationContext。

在頂層異步方法做到這一點:

async Task TopLevelAsync() 
{ 
    var syncContext = SynchronizationContext.Current; 
    SynchronizationContext.SetSynchronizationContext(null); 
    try 
    { 
     // no need for ConfigureAwait(false) 
     await SubTask1Async(); 
     await SubTask2Async(); 
    } 
    finally 
    { 
     SynchronizationContext.SetSynchronizationContext(syncContext); 
    } 
} 

一旦我已經與.ConfigureAwait一個函數內部(假)當前的SynchronizationContext將是無效和我並不需要關心關於它的子函數調用。那是對的嗎?

不幸的是,事實並非如此。這是一個危險的假設。這裏是爲什麼:

// in a top-level async method.. 
await FooAsync().ConfigureAwait(false); 

async Task FooAsync() 
{ 
    var result = await BarAsync().ConfigureAwait(false); 
    // we are inside the ConfigureAwait(false), or in the 
    // continuation after ConfigureAwait(false), so at this 
    // point the SynchronizationContext must be null, right? 
    // No it's not. 
} 

Task<bool> BarAsync() 
{ 
    return Task.FromResult(true); 
} 

BarAsync上面的例子中同步完成,這意味着背後的FooAsync方法狀態機繼續立即的執行,而無需重新調度。如果沒有重新調度邏輯,ConfigureAwait(false)不會記入帳戶,所以SynchronizationContext從不重置。

+0

在BarAsync的情況下,設計用於調用某些同步事件或模擬測試,可能的情況但不是非常標準的情況,因爲OP假設完整的異步調用鏈 –

+0

@MrinalKamboj,不一定。有很多這樣的真實代碼的例子。圖像,當你有多個數據提供者,消費者可以聚合他們。其中一個提供者可以是內存中的實現。但是,它們都實現了異步的相同接口,以確保在提供程序執行某種IO操作時不會阻塞線程。再次,在檢查結果「Task」變量的狀態之前,即使是一個真正的異步IO任務也可以完成。 –

+0

@MrinalKamboj,最基本的例子是'SemaphoreSlim',它首先使用旋轉鎖定,並且可以同步完成,而不是總是使用'WaitHandle',它提供了最佳性能。 C#7的一個特性是允許自定義類型,而不是使用Task或Task <7>',這是專門爲異步方法中的同步完成而設計的。 –

相關問題