今天要專(zhuān)門(mén)講一下Dispatcher,原因是WPF中經(jīng)常碰到多線(xiàn)程下軟件界面控件的更新問(wèn)題。相信很多初步接觸WPF的界面開(kāi)發(fā)的朋友,為了保持界面不卡,在一個(gè)自己創(chuàng )建的線(xiàn)程中去更新或者讀取一個(gè)控件時(shí)都會(huì )遇到了一個(gè)很奇怪的Exception異常,顯示如下:
this.Dispatcher.Invoke(()=> { // 你的訪(fǎng)問(wèn)空間或者改變控件代碼});XAML非常簡(jiǎn)單,代碼如下:
<Grid> <Button VerticalAlignment="Center" HorizontalAlignment="Center" Width="100" Name="myBtn">Click Me</Button> </Grid>C#后臺代碼如下:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Thread trd = new Thread(myFun);// 創(chuàng )建一個(gè)新線(xiàn)程 trd.Start();// 啟動(dòng)一個(gè)新線(xiàn)程 } public void myFun() { myBtn.Content = "My Love"; } } public void myFun() { this.Dispatcher.Invoke(()=> { myBtn.Content = "My Love"; }); }重新運行,線(xiàn)程正確無(wú)誤的修改了Button控件上的信息。
需要注意的是系統在MainWinow起來(lái)之后就給當前UI線(xiàn)程分配好了Dispatcher,這個(gè)Dispatcher屬于MainWindow這個(gè)實(shí)例的,原因在于MainWindow繼承自DispatcherObject類(lèi),而DispatcherObject包含了一個(gè)公共屬性Dispatcher。實(shí)際上不僅僅是Window類(lèi),其他控件也都繼承自DispatcherObject,因此他們在初始化時(shí)都自動(dòng)賦值了Dispatcher屬性,并且都指向同一個(gè)UI線(xiàn)程所擁有的Dispatcher對象。對于當前這例子,MainWindow和Button都被UI線(xiàn)程創(chuàng )建,并且MainWindow和Button的Dispatcher屬性都指向UI線(xiàn)程擁有的Dispatcher,因此,我們還可以將上面的例子改為下面的方式(用myBtn.Dispatcher.Invoke()來(lái)實(shí)現按鈕的更新,運行結果完全一樣):
public void myFun() { myBtn.Dispatcher.Invoke(()=> { myBtn.Content = "My Love"; }); } public MainWindow() { InitializeComponent(); myFun(); } public void myFun() { myBtn.Content = "My Love"; }我們發(fā)現,運行也沒(méi)問(wèn)題,沒(méi)有任何古怪的地方,加不加this.Dispatcher.Invoke()都可以運行ok。這是由于運行myFunc的環(huán)境是在UI線(xiàn)程之下。為了更容易發(fā)現線(xiàn)程的變換,我們加入更多的測試代碼,整個(gè)工程改為如下:
public MainWindow() { InitializeComponent(); Thread.CurrentThread.Name = "Main Thread";// 設置當前線(xiàn)程名稱(chēng) Thread trd = new Thread(myFun); // 創(chuàng )建一個(gè)新線(xiàn)程 trd.Name = "New Thread"; trd.Start(); // 啟動(dòng)一個(gè)新線(xiàn)程 } public void myFun() { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 }); }我們發(fā)現,代碼運行到myFun()時(shí)的線(xiàn)程已經(jīng)變成了trd所創(chuàng )建的線(xiàn)程(通過(guò)Thread.CurrentThread.Name來(lái)獲知當前線(xiàn)程名稱(chēng)是個(gè)好辦法),新線(xiàn)程的名稱(chēng)也成功輸出到按鈕上:
public void myFun() { // myBtn.VerifyAccess(); //該方法在不可訪(fǎng)問(wèn)的情況下,直接拋出InvalidOperationException異常 if(!myBtn.CheckAccess()) { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 }); } else { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 } } } public MainWindow() { InitializeComponent(); Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//結果:非null bool r = dsp.Equals(this.Dispatcher);//結果:true Thread.CurrentThread.Name = "Main Thread";// 設置當前線(xiàn)程名稱(chēng) Thread trd = new Thread(myFun); // 創(chuàng )建一個(gè)新線(xiàn)程 trd.Name = "New Thread"; trd.Start(); // 啟動(dòng)一個(gè)新線(xiàn)程 } public void myFun() { Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//結果:獲得的dsp值為null if (!myBtn.CheckAccess()) { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 }); } else { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 } } public MainWindow() { InitializeComponent(); Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//結果:非null bool r = dsp.Equals(this.Dispatcher);//結果:true Dispatcher newdsp = Dispatcher.CurrentDispatcher; // 獲取當前的Dispatcher r = this.Dispatcher.Equals(newdsp); //結果,仍然是true,說(shuō)明已經(jīng)有Dispatcher的情況下不會(huì )賦新Dispatcher Thread.CurrentThread.Name = "Main Thread";// 設置當前線(xiàn)程名稱(chēng) Thread trd = new Thread(myFun); // 創(chuàng )建一個(gè)新線(xiàn)程 trd.Name = "New Thread"; trd.Start(); // 啟動(dòng)一個(gè)新線(xiàn)程 } public void myFun() { Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread);//結果:獲得的dsp值為null Dispatcher newdsp = Dispatcher.CurrentDispatcher; // 默默的為當前新線(xiàn)程賦了一個(gè)Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread); // 重新獲取當前線(xiàn)程的Dispatcher bool r = newdsp.Equals(dsp);// 結果是true if (!myBtn.CheckAccess()) { this.Dispatcher.Invoke(() => { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 }); } else { myBtn.Content = Thread.CurrentThread.Name;// 將當前線(xiàn)程名稱(chēng)輸出到Button上 } }聯(lián)系客服