本人文筆差。還是直接上代碼吧。(本文假設你對WPF中的Dispatcher有一定的了解)
你覺得下面的代碼可以正常執行嗎?
private void Button_Click(object sender, RoutedEventArgs e) { Thread t = new Thread(() => { while (true) { new Window().Show(); Thread.Sleep(1000); } }); t.Start(); }
WPF的操作UI的線程必須是單線程單元模型(STA),也就是必須把線程的單元狀態設置為STA才可以操作UI對象。
上面的代碼,並沒有設置線程的單元狀態,線程的默認單元狀態為:System.Threading.ApartmentState.Unknown
通過下面的代碼可以設置線程的單元狀態為STA:
t.TrySetApartmentState(ApartmentState.STA);
修改后的代碼如下:(可以正常運行)
private void Button_Click(object sender, RoutedEventArgs e) { Thread t = new Thread(() => { while (true) { new Window().Show(); Thread.Sleep(1000); } }); t.TrySetApartmentState(ApartmentState.STA); t.Start(); }
接下來,對代碼再做一點修改,你覺得下面的代碼是否可以正常運行呢?
private void Button_Click(object sender, RoutedEventArgs e) { Thread t = new Thread(() => { while (true) { Window win = new Window(); this.Dispatcher.Invoke(new Action(delegate { win.Show(); })); Thread.Sleep(1000); } }); t.TrySetApartmentState(ApartmentState.STA); t.Start(); }
在一個線程中創建的UI對象,也只能是創建該UI對象的線程才能訪問它。如果其他線程要訪問這個UI對象,需要通過創建UI線程的Dispatcher才能實現。
上面的代碼,win對象實際上是t這個線程創建的。而執行win.Show()這個動作的卻是另外這個線程(實際上是主UI線程),所以上面的代碼也是不能正常執行的。
需要注意的是,只有當一個線程中執行過UI操作后,這個線程才具有Dispatcher,然后其他線程可以通過這個Dispatcher去訪問該線程創建的UI對象。一個沒有執行任何UI操作的線程,其Dispatcher為null.
通過Dispatcher的靜態方法FromThread可以獲取一個線程關聯的Dispatcher對象。如果你覺得上面的這句話不好理解,可以看看下面的圖:
看以看到,在沒有執行UI操作之前,線程t的Dispatcher對象的值為null,當執行完Window win = new Window();后(也就是執行了一個UI操作),t線程關聯的Dispatcher就有值了。
總結:
- 任何線程中如果想執行UI操作,那么其線程單元必須設置為STA。
- 一個線程如果創建了UI對象,那么這個UI對象就只能被這個線程管理。
- 任何線程如果需要訪問其他的線程創建的UI對象,只能通過其他線程的Dispatcher進行訪問
- 一個線程如果沒有執行任何UI操作,那么其關聯的Dispatcher為null