題記
在編寫有GUI的程序時,會遇到這樣一種情形:用戶點擊了一個按鈕,程序處理這個事件,然而這個處理過程耗時間較長。我們不想讓軟件卡在這里,而是讓用戶可以繼續使用其他的軟件功能。這種問題可以用多線程的事件響應來解決。這里,我就WPF的多線程事件響應做一個簡單的歸納。
一、簡單的異步的事件響應
在WPF中,針對簡單的多線程處理過程,我們可以使用.NET自帶的BackgroundWork完成。BackgroundWork的處理過程就是異步的,不會讓用戶界面停止響應。
using System.ComponentModel; using System.Threading; namespace TestProject { public partial class MainWindow : Window { //... private void Button1_Click(object sender, RoutedEventArgs e) { //聲明 BackgroundWorker worker = new BackgroundWorker(); // worker 要做的事情 使用了匿名的事件響應函數 worker.DoWork += (o, ea) => { //WPF中線程只能控制自己創建的控件, //如果要修改主線程創建的MainWindow界面的內容, //可以委托主線程的Dispatcher處理。 //在這里,委托內容為一個匿名的Action對象。 this.Dispatcher.Invoke((Action)(() => { this.TextBox1.Text = "worker started"; })); Thread.Sleep(1000); }; // worker 完成事件響應 worker.RunWorkerCompleted += (o, ea) => { this.Dispatcher.Invoke((Action)(() => { this.TextBox1.Text = "worker finished"; })); }; //注意:運行了下面這一行代碼,worker才真正開始工作。上面都只是聲明定義而已。 worker.RunWorkerAsync(); } //... } }
二、自定義事件的多線程處理過程
有時候,在我們創建的新的線程中,可能有一些事件需要主線程處理。對於這種比較復雜的異步處理,可以自定義事件,用C#中的委托(delegate)實現。
MyThread.cs (自定義事件)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace TestProject { //定義 自定義事件的參數類型 public class MyEventArgs : EventArgs { //參數可以攜帶值,方便處理程序使用 public readonly int value; public MyEventArgs(int v) { value = v; } } class MyThread { //... //定義delegate public delegate void MyEventHandler(object sender, MyEventArgs e); //聲明 自定義事件 public event MyEventHandler MyEvent; //... //觸發事件 protected virtual void OnMyEvent(MyEventArgs e) { if (MyEvent != null) { MyEvent(this, e); } } //... //測試函數 public void Test() { System.Threading.Thread.Sleep(1000); this.OnMyEvent(new MyEventArgs(100)); System.Threading.Thread.Sleep(2000); } } }
MainWindows.xaml.cs
using System.ComponentModel; using System.Threading; namespace TestProject { public partial class MainWindow : Window { //... //測試 private void Button2_Click(object sender, RoutedEventArgs e) { MyThread myThread = new MyThread(); //為myThread的MyEvent事件聲明一個響應函數 myThread.MyEvent += MyThread_MyEvent; //定義新的線程 Thread thread = new Thread(new ThreadStart(myThread.Test)); //開始新的線程 thread.Start(); } // 新的線程中 MyEvent 的響應函數 public void MyThread_MyEvent(object sender, MyEventArgs e) { //同樣,如果想要修改主界面的控件, //需要委托主線程的Dispatcher來處理。 this.Dispatcher.Invoke((Action)(() => { this.TextBox2.Text = "MyEvent triggered"; })); } //... } }
總結
以上介紹了兩種情況下,WPF多線程的實現方法。第一種可以滿足一般的需求,例如:后台加載文件。第二種主要針對后台處理時有特殊的事件需要前台響應,例如:后台加載文件,每當加載滿1M時,在MainWindow展示特定內容給用戶。
