本文通過介紹C#多線程的用法(基礎玩法),附加介紹一下WinForm里邊跨線程訪問UI的方法
如圖,就是這么一個簡單的界面,每個按鈕下面一個方法,分別設置文本框里邊的內容,那么,開始吧!
先介紹一下WinForm的線程模型:WinForm 是通過調用Windows API 的GetMessage Or PeekMeeage來處理其他線程發送過來的消息,這些消息存儲在系統的一個消息隊列中,創建主界面的線程就是主線程(UI線程),UI線程負責消費該消息隊列中的消息。
WinForm框架中有一個ISynchronizeInvoke接口,所有的UI元素都繼承自該接口,接口中的InvokeRequired屬性表示了當前線程是否是創建它的線程,接口中的BeginInvoke or Invoke 負責將消息發送到消息隊列,這樣UI線程就能夠正確的訪問它了。
那么,首先看代碼片段一:里邊就實現了將設置文本框內容的消息發送到了消息隊列
private void SetMessage(string message) { if (this.txtMsg.InvokeRequired) { //BeginInvoke or Invoke 負責將消息發送到消息隊列 this.txtMsg.BeginInvoke(new Action<string>((msg) => { this.txtMsg.Text = msg; }), message); } else { this.txtMsg.Text = message; } }
代碼片段二Thread:Thread可能是用的最多的了,也是最早的框架里邊就有的。這種寫法很簡單,也很方便,需要提一下的就是IsBackground屬性,IsBackground=true表示為后台線程,應用程序退出,哪怕任務沒有執行完,也會退出;IsBackground=false表示為前台線程,默認為false,應用程序退出,只要任務還沒有執行完,進程就不會結束。
private void btnThread_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { SetMessage("Thread 跨線程訪問UI"); }); //IsBackground=true表示為后台線程 應用程序退出 哪怕任務沒有執行完 也會退出 //IsBackground=false表示為后台線程 默認為false 應用程序退出 只要任務還沒有執行完 進程就不會結束 thread.IsBackground = true; thread.Start(); }
代碼片段三ThreadPool:ThreadPool是微軟為了避免開發人員,無節制的使用線程,而提供的一個線程管理類
private void btnThreadPool_Click(object sender, EventArgs e) { //線程池 是微軟為了避免開發人員 無節制的使用線程 而提供的一個線程管理類 ThreadPool.QueueUserWorkItem((obj) => { SetMessage("ThreadPool 跨線程訪問UI"); }, null); }
代碼片段四Task:Task有很多的優勢,也是后面高版本才推出來的,推薦使用。Task與Thread的區別就是:Task使用的是線程池中的線程,Task較之線程池的優勢是:
1.Task支持取消,完成,失敗通知等交互性操作
2.Task支持線程執行的先后次序
private void btnTask_Click(object sender, EventArgs e) { /* Task與Thread的區別就是:Task使用的是線程池中的線程 * Task較之線程池的優勢是: * 1.Task支持取消,完成,失敗通知等交互性操作 * 2.Task支持線程執行的先后次序*/ Task.Factory.StartNew(() => { SetMessage("Task 跨線程訪問UI"); }); Task task = new Task(()=> { SetMessage("Task 跨線程訪問UI"); }); task.Start(); }
代碼片段五BackgroundWorker:BackgroundWorker內部是通過線程池實現的,通過事件提供了跨線程訪問UI的能力,這個做CS開發,是用的最多的,說白了,太好用。
//BackgroundWorker 內部是通過線程池實現的 //BackgroundWorker 通過事件提供了跨線程訪問UI的能力 BackgroundWorker _bgw = new BackgroundWorker(); private void btnBackgroundWorker_Click(object sender, EventArgs e) { _bgw.WorkerReportsProgress = true; _bgw.WorkerSupportsCancellation = true; _bgw.DoWork += _bgw_DoWork; ; _bgw.ProgressChanged += _bgw_ProgressChanged; _bgw.RunWorkerCompleted += _bgw_RunWorkerCompleted; if (!_bgw.IsBusy) { _bgw.RunWorkerAsync(); } } private void _bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.txtMsg.Text = "BackgroundWorker 跨線程訪問UI";//注意這里是直接訪問UI } private void _bgw_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.txtMsg.Text = e.UserState.ToString();//注意這里是直接訪問UI } private void _bgw_DoWork(object sender, DoWorkEventArgs e) { for (int i = 0; i < 5; i++) { Thread.Sleep(1000); _bgw.ReportProgress(i, $"{i}秒"); } }
代碼片段六SynchronizationContext:SynchronizationContext同步上下文在通訊中充當傳輸者的角色,實現功能就是一個線程和另外一個線程的通訊,這個在跨線程一次性要更新很多的UI控件的時候,非常的適用。
//SynchronizationContext 在通訊中充當傳輸者的角色,實現功能就是一個線程和另外一個線程的通訊 SynchronizationContext _syncContext; private void btnSynchronizationContext_Click(object sender, EventArgs e) { _syncContext = SynchronizationContext.Current; Thread thread = new Thread(() => { if (_syncContext != null) { SendOrPostCallback callBack = (obj) => { //在某個子線程里 一次性要更新很多UI控件的時候 用這個方法 很nice this.txtMsg.Text = "SynchronizationContext 跨線程訪問UI"; }; _syncContext.Post(callBack, null);//異步 //_syncContext.Send(callBack, null);//同步 } }); thread.IsBackground = true; thread.Start(); }
合並之后的代碼為:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace ThreadChapter { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnThread_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { SetMessage("Thread 跨線程訪問UI"); }); //IsBackground=true表示為后台線程 應用程序退出 哪怕任務沒有執行完 也會退出 //IsBackground=false表示為后台線程 默認為false 應用程序退出 只要任務還沒有執行完 進程就不會結束 thread.IsBackground = true; thread.Start(); } /* * WinForm 是通過調用Window API 的GetMessage Or PeekMeeage來處理其他線程發送過來的消息, * 這些消息存儲在系統的一個消息隊列中,創建主界面的線程就是主線程(UI線程),UI線程負責處理該消息隊列 */ private void SetMessage(string message) { if (this.txtMsg.InvokeRequired) { //BeginInvoke or Invoke 負責將消息發送到消息隊列 this.txtMsg.BeginInvoke(new Action<string>((msg) => { this.txtMsg.Text = msg; }), message); } else { this.txtMsg.Text = message; } } private void btnThreadPool_Click(object sender, EventArgs e) { //線程池 是微軟為了避免開發人員 無節制的使用線程 而提供的一個線程管理類 ThreadPool.QueueUserWorkItem((obj) => { SetMessage("ThreadPool 跨線程訪問UI"); }, null); } private void btnTask_Click(object sender, EventArgs e) { /* Task與Thread的區別就是:Task使用的是線程池中的線程 * Task較之線程池的優勢是: * 1.Task支持取消,完成,失敗通知等交互性操作 * 2.Task支持線程執行的先后次序*/ Task.Factory.StartNew(() => { SetMessage("Task 跨線程訪問UI"); }); } //BackgroundWorker 內部是通過線程池實現的 //BackgroundWorker 通過事件提供了跨線程訪問UI的能力 BackgroundWorker _bgw = new BackgroundWorker(); private void btnBackgroundWorker_Click(object sender, EventArgs e) { _bgw.WorkerReportsProgress = true; _bgw.WorkerSupportsCancellation = true; _bgw.DoWork += _bgw_DoWork; ; _bgw.ProgressChanged += _bgw_ProgressChanged; _bgw.RunWorkerCompleted += _bgw_RunWorkerCompleted; if (!_bgw.IsBusy) { _bgw.RunWorkerAsync(); } } private void _bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.txtMsg.Text = "BackgroundWorker 跨線程訪問UI";//注意這里是直接訪問UI } private void _bgw_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.txtMsg.Text = e.UserState.ToString();//注意這里是直接訪問UI } private void _bgw_DoWork(object sender, DoWorkEventArgs e) { for (int i = 0; i < 5; i++) { Thread.Sleep(1000); _bgw.ReportProgress(i, $"{i}秒"); } } //SynchronizationContext 在通訊中充當傳輸者的角色,實現功能就是一個線程和另外一個線程的通訊 SynchronizationContext _syncContext; private void btnSynchronizationContext_Click(object sender, EventArgs e) { _syncContext = SynchronizationContext.Current; Thread thread = new Thread(() => { if (_syncContext != null) { SendOrPostCallback callBack = (obj) => { //在某個子線程里 一次性要更新很多UI控件的時候 用這個方法 很nice this.txtMsg.Text = "SynchronizationContext 跨線程訪問UI"; }; _syncContext.Post(callBack, null);//異步 //_syncContext.Send(callBack, null);//同步 } }); thread.IsBackground = true; thread.Start(); } } }