2015-04-01 47 views
5

閱讀Stephen Toub's article on SynchronizationContext我留下了一個關於這一塊的.NET 4.5代碼的輸出問題後:的SynchronizationContext上Task.Run流而不是在等待

private void btnDoSomething_Click() 
{ 
    LogSyncContext("btnDoSomething_Click"); 
    DoItAsync().Wait(); 
} 
private async Task DoItAsync() 
{ 
    LogSyncContext("DoItAsync"); 
    await PerformServiceCall().ConfigureAwait(false); //to avoid deadlocking 
} 
private async Task PerformServiceCall() 
{ 
    LogSyncContext("PerformServiceCall 1"); 
    HttpResponseMessage message = await new HttpClient 
    { 
     BaseAddress = new Uri("http://my-service") 
    } 
    .GetAsync("/").ConfigureAwait(false); //to avoid deadlocking 
    LogSyncContext("PerformServiceCall 2"); 
    await ProcessMessage(message); 
    LogSyncContext("PerformServiceCall 3"); 
} 

private async Task ProcessMessage(HttpResponseMessage message) 
{ 
    LogSyncContext("ProcessMessage"); 
    string data = await message.Content.ReadAsStringAsync(); 
    //do something with data 
} 

private static void LogSyncContext(string statementId) 
{ 
    Trace.WriteLine(String.Format("{0} {1}", statementId, SynchronizationContext.Current != null ? SynchronizationContext.Current.GetType().Name : TaskScheduler.Current.GetType().Name)); 
} 

輸出是:

btnDoSomething_Click WindowsFormsSynchronizationContext

DoItAsync WindowsFormsSynchronizationContext

PerformServiceCall 1 WindowsFormsSynchronizationContext

PerformServiceCall 2 ThreadPoolTask​​Scheduler

ProcessMessage的ThreadPoolTask​​Scheduler

PerformServiceCall 3 ThreadPoolTask​​Scheduler

但我希望PerformServiceCall 1至不能上WindowsFormsSynchronizationContext自文章指出,「的SynchronizationContext。目前沒有「流過」等待點「...

上下文與Task.Run和異步調用拉姆達時PerformServiceCall沒有獲得通過,像這樣:

await Task.Run(async() => 
{ 
    await PerformServiceCall(); 
}).ConfigureAwait(false); 

任何人都可以澄清或者點一些這方面的資料?

+1

直到Task實際開始等待,ConfigureAwait()調用纔會有任何影響。那還沒有發生,你的LogSyncContext()調用要早。在等待之後移動它。 – 2015-04-01 09:48:06

+0

是不是'DoItAsync()等待();'? – 2015-04-01 10:40:41

+0

不,它不會由於ConfigureAwait調用而發生死鎖 – Stif 2015-04-02 13:27:36

回答

6

Stephen的文章解釋說SynchronizationContext不像ExecutionContext那樣「流動」(儘管SynchronizationContextExecutionContext的一部分)。

ExecutionContext總是流淌。即使當你使用Task.Run,所以如果SynchronizationContext會與它流動Task.Run將在UI線程上執行,因此Task.Run將毫無意義。 SynchronizationContext不會流動,而是在到達異步點(即await)並將其發佈到它後繼續(除非另有明確說明)時捕獲它。

的差額,這句話解釋說:

現在,我們有一個非常重要的觀察使:流動ExecutionContext在語義上比捕捉和張貼到一個SynchronizationContext非常不同。

當您流動ExecutionContext時,您從一個線程捕獲狀態,然後恢復該狀態,使其在提供的委託執行過程中處於環境狀態。當您捕獲並使用SynchronizationContext時,不會發生這種情況。捕捉部分是相同的,因爲你從當前線程獲取數據,但是你以不同的方式使用該狀態。而不是在調用委託期間使當前狀態爲最新狀態,SynchronizationContext.Post您只是使用捕獲狀態來調用委託。代表運行的地點,時間和方式完全取決於實施Post方法。

這意味着你的情況,當你輸出PerformServiceCall 1目前SynchronizationContext確實WindowsFormsSynchronizationContext,因爲你還沒有達成任何異步點,你仍然在UI線程(記住,前第一部分awaitasync方法在調用線程上同步執行,所以LogSyncContext("PerformServiceCall 1");發生在從PerformServiceCall返回的任務上發生ConfigureAwait(false))。

當您使用ConfigureAwait(false)(忽略捕獲的SynchronizationContext)時,您只能「下車」UI的SynchronizationContext。第一次發生的是HttpClient.GetAsync,然後是PerformServiceCall