WPF 學習筆記 - 2. Dispatcher

 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();
}

 

uploads/200808/11_191313_wpf.png



按照 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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章