WPF 使用一個專用的 UI 線程來完成界面的操作和更新,這個線程會關聯一個唯一的 Dispatcher 對象,用於調度按優先順序排列的工作項隊列。Application.Run() 實際上就是對 Dispatcher.Run() 的間接調用。
Dispatcher 通過循環來處理工作項隊列,這個循環通常被成為 "幀 (DispatcherFrame)"。Dispatcher.Run() 創建並啟動這個幀,這也是 Application.Run() 啟動消息循環的最終途徑。
public sealed class Dispatcher
{
[SecurityCritical, UIPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public static void Run()
{
PushFrame(new DispatcherFrame());
}
}
DispatcherFrame 可以嵌套,並通過檢查 Continue 屬性來決定循環是否繼續。我們可以通過調用 Dispatcher.ExitAllFrames() 來終止所有的幀循環,當然這種編程方式並不可取,可能會造成一些意外出現。
與 Dispatcher 調度對象想對應的就是 DispatcherObject,在 WPF 中絕大部分控件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的對象具有線程關聯特征,也就意味著只有創建這些對象實例,且包含了 Dispatcher 的線程(通常指默認 UI 線程)才能直接對其進行更新操作。
當我們嘗試從一個非 UI 線程更新一個標簽,會看到一個如下的異常。
private void button1_Click(object sender, RoutedEventArgs e)
{
new Thread(() => this.label1.Content = DateTime.Now.ToString()).Start();
}
按照 DispatcherObject 的限制原則,我們改用 Window.Dispatcher.Invoke() 即可順利完成這個更新操作。
private void button1_Click(object sender, RoutedEventArgs e)
{
new Thread(() =>
{
this.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(() => this.label1.Content = DateTime.Now.ToString()));
}).Start();
}
如果在其他項目(比如類庫)中,我們可以用 Application.Current.Dispatcher.Invoke(...) 完成同樣的操作,它們都指向 UI Thread Dispatcher 這個唯一對象。
Dispatcher 還提供了 BeginInvoke 這個異步版本。
private void button1_Click(object sender, RoutedEventArgs e)
{
new Thread(() =>
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
Thread.Sleep(3000);
this.label1.Content = DateTime.Now.ToString();
}));
MessageBox.Show("Hi!");
}).Start();
}
凡事都有例外,WPF 還提供了一種繼承自 Freezable 的類型,盡管 Freezable 也間接繼承自 DispatcherObject,但當這類對象從修改狀態變成凍結狀態時,它即變成自由線程對象,不在具有線程關聯。(有關 Freezable 詳情可參考 MSDN)