2013-04-25 25 views
7

執行關於這段代碼:爲什麼異步回調在WPF UI線程

static async Task<string> testc() 
{ 
    Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId); 
    await Task.Run(() => { 
     Thread.Sleep(1000); 
     Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId); 
    }); 
    Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId); 
    return "bob"; 
} 

static void Main(string[] args) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

我得到以下輸出:

helo sync 10 
helo async 10 
over10 
task 11 
callback **11** 

這是確定:的await後的代碼中執行與任務本身相同的線程。

現在,如果我做一個WPF應用程序:

private void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

它會生成以下的輸出:

helo sync 8 
helo async 8 
over8 
task 9 
callback **8** 

這裏我們可以看到代碼後,在UI線程伺機執行。那麼,這是偉大的,因爲它可以操縱可觀察的集合等......但我想知道「爲什麼?」 「我怎麼能這樣做?」這與某些TaskScheduler行爲有關嗎?這是在.NET Framework中硬編碼的嗎?

Thx對於您可能提交的任何想法。

回答

7

原因是,Task.Run將捕獲SynchronizationContext,如果其中一個在WPF應用程序中存在,則從UI線程啓動該任務時會出現該問題。然後任務將使用SynchronizationContext將回調序列化到UI線程。但是,如果在控制檯應用程序中沒有上下文可用,則回調將發生在另一個線程上。

Stephen Toub在blog entry中描述了這一點。

順便說一句, 使用時要小心 永遠不要在任務中使用Thread.Sleep。這可能會導致奇怪的行爲,因爲任務可能不會綁定到一個線程。改爲使用Task.Delay。

+0

+1需要更多的重視您的BTW。 – Phill 2013-04-25 08:07:22

2

但我想知道「爲什麼?」

你已經回答了你自己:

那麼,這是偉大的,因爲它使操控觀察集合等..

異步的全部意義就是使異步容易與 - 一起工作 - 這樣您就可以編寫實際上是異步的「同步查看」代碼。這通常包括想要在整個異步方法的一個上下文中(例如一個UI線程) - 只需在需要等待某些事情時「暫停」該方法(不會阻塞UI線程)。

「我怎麼能這樣做?」

目前尚不清楚您的意思。基本上,Task的等待模式的實現使用TaskScheduler.FromCurrentSynchronizationContext()來確定哪個調度程序發佈回調 - 除非您呼叫ConfigureAwait(false)明確選擇退出此行爲。所以這就是它如何管理它......你是否可以「做同樣的事情」取決於你想要做什麼。

有關等待模式的更多詳情,請參閱async/await FAQ中的「什麼是awaitables」問題。

+0

「我怎麼能這樣做?」我認爲他的意思是:「如何在不使用WPF的情況下獲得WPF行爲?」 – 2013-04-25 06:20:16

+0

@ jdv-JandeVaan:如果這就是含義,那就非常不清楚了。無論如何,希望我所提供的細節能夠提供足夠的信息來進一步提高OP。如有必要,他們總是可以要求更多細節。 – 2013-04-25 06:23:31

+0

好的。好的。我真的不想這樣做。我想知道如何將回調發送到Dispatcher.Invoke方法......並且如果我可以,最終做類似的事情,或者如果它在框架中被硬編碼。 Thx爲您的答案 – Kek 2013-04-25 06:34:57

0

您可能會發現我的async/await intro有幫助。其他的答案是差不多是正確的。

當您尚未完成的await a Task默認情況下會捕獲「上下文」,用於在Task完成時恢復該方法。除非爲空,否則此「上下文」爲SynchronizationContext.Current,在這種情況下爲TaskScheduler.Current

注意工作需要這種條件:

  • 「當你await ......」 - 如果你手動計劃的延續,例如,與Task.ContinueWith,沒有上下文捕獲完成。你必須用(SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext())之類的東西自己動手。
  • 「...... await一個Task ......」 - 這種行爲是爲Task類型的await行爲的一部分。其他類型可能會或可能不會做類似的捕獲。
  • 「......還沒有完成......」 - 如果Task是已經被時間完成它的await版,該async方法將繼續同步。所以在這種情況下不需要捕獲上下文。
  • 「......默認情況下...」 - 這是默認行爲,可以更改。特別是,請撥打Task.ConfigureAwait方法,並通過false獲取continueOnCapturedContext參數。此方法返回一個等待類型(不是Task),如果其參數爲false,則不會在捕獲的上下文中繼續。
+0

Thx爲此完整答案。大部分(或者至少足以回答我的問題)在@Jakob Christensen的回答中,但我讚賞這個觀點! – Kek 2013-04-25 13:30:55