![](/img/trans.png)
[英]Control.Dispatcher.BeginInvoke() and Control.Dispatcher.Invoke() order execution is confusing?
[英]What is the order of execution for Dispatcher operations(Invoke/BeginInvoke)
這是WPF中的測試代碼:
Action ac0 = delegate
{
Console.WriteLine("action-beginInvoke-enter");
Application.Current.Dispatcher.BeginInvoke(new Action(delegate
{
Console.WriteLine("dispatcher begin invoke code");
}));
Console.WriteLine("action-beginInvoke-exit");
};
ac0.BeginInvoke(null, null);
Console.WriteLine("ui thread sleep before");
Thread.Sleep(1000);// ensure the ac0 is done
Console.WriteLine("ui thread sleep after");
Application.Current.Dispatcher.Invoke(new Action(delegate
{
Console.WriteLine("dispatcher invoke code");
}));
Console.WriteLine("ui thread exit");
Output:
ui線程睡眠之前
action-beginInvoke-enter
動作開始調用退出
ui線程睡眠后
調度程序調用代碼
ui線程退出
調度程序開始調用代碼
然后,我將 Invoke 代碼刪除到另一個項目中並編譯為 a.dll:
Action ac0 = delegate
{
Console.WriteLine("action-beginInvoke-enter");
Application.Current.Dispatcher.BeginInvoke(new Action(delegate
{
Console.WriteLine("dispatcher begin invoke code");
}));
Console.WriteLine("action-beginInvoke-exit");
};
ac0.BeginInvoke(null, null);
Console.WriteLine("ui thread sleep before");
Thread.Sleep(1000);// ensure the ac0 is done
Console.WriteLine("ui thread sleep after");
Test.WriteLine();// Test is a static class from dll
Console.WriteLine("ui thread exit");
這是 Test.WriteLine:
Application.Current.Dispatcher.Invoke(new Action(delegate
{
Console.WriteLine("dispatcher invoke code");
}));
output 是:
ui線程睡眠之前
action-beginInvoke-enter
動作開始調用退出
ui線程睡眠后
調度程序開始調用代碼
調度程序調用代碼
ui線程退出
我試圖弄清楚 Dispatcher 操作的執行順序。 據我所知,UI 線程一直很忙,直到執行到最后一行。 在此之前它如何執行代碼“調度程序開始調用代碼”? 並且代碼是相同的,除了 Dispatcher.Invoke 被刪除到 dll 中。 為什么他們的 output 不一樣?
對您的問題的簡短回答 - 執行順序是什么 - 是,您擁有它的方式是不可預測的。 但是我們可以通過使用TaskCompletionSource
使其可預測。
長答案 - 你在這里發生了一些不同的事情,我認為這是你意想不到的結果的根源。
首先,您在ac0
上使用Action.BeginInvoke
。 這異步調用ac0
,即在后台,可能(如果不總是?)在與調用BeginInvoke
的線程不同的線程上。 除非您采取特定步驟(我將在下面概述),否則委托與您的其他代碼的執行順序是未定義的,並且可能看起來是隨機的。
其次,在委托ac0
(它再次異步運行)內部,您將使用另一個委托調用Dispatcher.Invoke
。 我們稱之為ac0_1
。 ac0_1
是實際寫入控制台的內容。 Dispatcher.Invoke
與Action.BeginInvoke
的不同之處在於,您提供給Dispatcher.Invoke
的任何內容始終在可預測的線程(UI 線程)上執行。 (使用Action.BeginInvoke
您不知道它將在哪個線程上執行,除非它(當然?)不會是 UI 線程)。 Dispatcher.Invoke
的不同之處還在於它在委托完成之前不會返回 - 這意味着它會阻塞調用線程 - 這使得與異步操作混合非常危險。
因此,通過混合Dispatcher.Invoke
和Action.BeginInvoke
,您混合了蘋果和橙子,並且很少會得到可預測的結果。 因此,除非您要處理無法管理的回調鏈和/或總應用程序鎖定的高可能性,否則 IMO 保證代碼按您期望的順序執行的最佳方法是使用TaskCompletionSource
。 它會像這樣工作:
TaskCompletionSource<object> task1 = new TaskCompletionSource<object>();
Action ac0 = delegate
{
Application.Current.Dispatcher.InvokeAsync(new Action(delegate
{
Console.WriteLine("ac0");
task1.SetResult(null);
}));
};
ac0.BeginInvoke(null, null);
await task1.Task; // ensure the ac0 is done
TaskCompletionSource<object> task2 = new TaskCompletionSource<object>();
Application.Current.Dispatcher.InvokeAsync(new Action(delegate
{
Console.WriteLine("ac1");
task2.SetResult(null);
}));
await task2.Task;
Console.WriteLine("over");
幕后發生的事情是await task1.Task
之后的所有內容本質上都變成了在調用task1.SetResult(null)
時執行的回調,但它仍然允許您編寫具有明顯線性執行流程的代碼。
您還會注意到我將Dispatcher.Invoke
更改為Dispatcher.InvokeAsync
。 為什么? 避免鎖定的可能性。 當您第一次調用Dispatcher.Invoke
時,我們不知道哪個線程實際上處於活動狀態,但無論它是什么都將被阻塞,直到委托完成。 由於我們實際上從不想阻塞線程(因此實際上從不想使用 Thread.Sleep),所以最好始終使用這些方法的異步版本並使用TaskCompletionSource
來獲取等待的Task
,以確保項目在繼續之前已完成。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.