C# 執行緒(Thread)的常見解決方案
阿新 • • 發佈:2019-01-08
前言:多執行緒程式設計是應用程式開發中一個非常重要的部分,這裡總結一些常見的執行緒(Thread)解決方案。注意:這裡僅涉及Thread的常見解決方案,CLR 4.0以後的Task這裡不做描述。
目錄
1、使用BackgroundWorker元件2、執行緒計時器3、執行緒池4、執行緒同步5、總結一、使用BackgroundWorker元件1、 事件呼叫要執行的後臺操作;WorkerReportsProgress屬性要設為true);我們看一個簡單的示例:可以通過程式設計方式建立 物件,在WinForm中也可以將它從“工具箱”的“元件”選項卡中拖到窗體上。我們看一下它的事件:
建立Windows窗體應用程式。新增一個名為resultLabel
可以看到無論是反饋操作進度,還是操作完成觸發事件,亦或是請求取消後臺操作,都非常方便,當涉及單個執行緒的操作時,優先推薦使用 ,而不是Thread。二、執行緒計時器類可用於定期在單獨的執行緒上執行任務。例如,可以使用執行緒計時器來檢查資料庫的狀態和完整性,或備份重要檔案。using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace MyLib.WinFormEx { public partial class BackgroundWorkerForm : Form { public BackgroundWorkerForm() { InitializeComponent(); backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.WorkerSupportsCancellation = true; // DoWork 事件呼叫要執行的後臺操作 backgroundWorker1.DoWork += backgroundWorker1_DoWork; // ProgressChanged 事件更新後臺操作進度資訊(WorkerReportsProgress屬性要設為true) backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged; // RunWorkerCompleted 事件在後臺操作完成時觸發 backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted; } private void startAsyncButton_Click(object sender, EventArgs e) { if (backgroundWorker1.IsBusy != true) { // 開始執行後臺操作 backgroundWorker1.RunWorkerAsync(); } } private void cancelAsyncButton_Click(object sender, EventArgs e) { if (backgroundWorker1.WorkerSupportsCancellation == true) { // 請求取消後臺操作 backgroundWorker1.CancelAsync(); } } // DoWork 事件呼叫要執行的後臺操作; private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; for (int i = 1; i <= 10; i++) { if (worker.CancellationPending == true) { e.Cancel = true; break; } else { // 執行耗時的操作並報告進度 System.Threading.Thread.Sleep(500); worker.ReportProgress(i * 10); } } } // ProgressChanged 事件更新後臺操作進度資訊(WorkerReportsProgress屬性要設為true); private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { resultLabel.Text = (e.ProgressPercentage.ToString() + "%"); } // RunWorkerCompleted 事件在後臺操作完成時觸發; private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled == true) { resultLabel.Text = "Canceled!"; } else if (e.Error != null) { resultLabel.Text = "Error: " + e.Error.Message; } else { resultLabel.Text = "Done!"; } } } }
三、執行緒池“執行緒池”是可以用來在後臺執行多個任務的執行緒集合。 這使主執行緒可以自由地非同步執行其他任務。執行緒池通常用於伺服器應用程式。每個傳入請求都將分配給執行緒池中的一個執行緒,因此可以非同步處理請求,而不會佔用主執行緒,也不會延遲後續請求的處理。一旦池中的某個執行緒完成任務,它將返回到等待執行緒佇列中,等待被再次使用。 這種重用使應用程式可以避免為每個任務建立新執行緒的開銷。執行緒池通常具有最大執行緒數限制。 如果所有執行緒都繁忙,則額外的任務將放入佇列中,直到有執行緒可用時才能夠得到處理。雖然可以實現自己的執行緒池,但是強烈推薦通過 類使用 .NET Framework 提供的執行緒池。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MyLib.ConApp.ThreadTest { public class ThreadTimerProgram { public static void Main(string[] args) { // 建立事件以在計時器回撥中訊號超時計數閾值。 AutoResetEvent autoEvent = new AutoResetEvent(false); StatusChecker statusChecker = new StatusChecker(10); // 建立呼叫計時器方法的推斷委託。 TimerCallback tcb = statusChecker.CheckStatus; // 建立一個計時器, 它指示委託在一秒鐘之後呼叫 CheckStatus, 此後每隔1/4 秒發出一次訊號。 Console.WriteLine("{0} 建立計時器.\n", DateTime.Now.ToString("h:mm:ss.fff")); Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250); // 當自動事件訊號時, 將週期更改為每1/2 秒。 autoEvent.WaitOne(5000, false); stateTimer.Change(0, 500); Console.WriteLine("\n更改期間.\n"); // 當自動事件第二次發出訊號時, 釋放計時器。 autoEvent.WaitOne(5000, false); stateTimer.Dispose(); Console.WriteLine("\n銷燬計時器."); } } class StatusChecker { private int invokeCount; private int maxCount; public StatusChecker(int count) { invokeCount = 0; maxCount = count; } // 此方法由計時器委託呼叫。 public void CheckStatus(Object stateInfo) { AutoResetEvent autoEvent = (AutoResetEvent)stateInfo; Console.WriteLine("{0} 正在檢查狀態 {1,2}.", DateTime.Now.ToString("h:mm:ss.fff"), (++invokeCount).ToString()); if (invokeCount == maxCount) { // 重置計數器和訊號主鍵。 invokeCount = 0; autoEvent.Set(); } } } }
public void DoWork() { // 對任務進行排隊。 System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(SomeLongTask)); // 對另一個任務進行排隊。 System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(AnotherLongTask)); } private void SomeLongTask(Object state) { // 插入程式碼以執行長任務。 } private void AnotherLongTask(Object state) { // 插入程式碼以執行長任務。 }
4、執行緒同步執行緒的非同步特性意味著必須協調對資源(如檔案控制代碼、網路連線和記憶體)的訪問。 否則,兩個或更多的執行緒可能在同一時間訪問相同的資源,而每個執行緒都不知道其他執行緒的操作。 結果將產生不可預知的資料損壞。 4.1 鎖(lock) lock (C#) 語句可以用來確保程式碼塊完成執行,而不會被其他執行緒中斷。 這是通過在程式碼塊執行期間為給定物件獲取互斥鎖來實現的。lock 語句有一個作為引數的物件,在該引數的後面還有一個一次只能由一個執行緒執行的程式碼塊。
public class TestThreading
{
private System.Object lockThis = new System.Object();
public void Process()
{
lock (lockThis)
{
// 訪問執行緒敏感的資源。
}
}
}
提供給 lock 關鍵字的引數必須為基於引用型別的物件,該物件用來定義鎖的範圍。強烈建議避免鎖定 public 型別或鎖定不受應用程式控制的物件例項。 4.2 同步事件和等待控制代碼 使用鎖或監視器對於防止同時執行區分執行緒的程式碼塊很有用,但是這些構造不允許一個執行緒向另一個執行緒傳達事件。這要“同步事件”,它是有兩個狀態(終止和非終止)的物件,可以用來啟用和掛起執行緒。讓執行緒等待非終止的同步事件可以將執行緒掛起,將事件狀態更改為終止可以將執行緒啟用。 如果執行緒嘗試等待已經終止的事件,則執行緒將繼續執行,而不會延遲。 它們之間唯一的不同在於,無論何時,只要 啟用執行緒,它的狀態將自動從終止變為非終止。 相反, 允許它的終止狀態啟用任意多個執行緒,只有當它的 Reset 方法被呼叫時才還原到非終止狀態。class Program
{
static AutoResetEvent autoEvent;
static void DoWork()
{
Console.WriteLine("3、工作執行緒已啟動, 正在等待事件...");
autoEvent.WaitOne();
Console.WriteLine("5、工作執行緒已重新啟用, 現在退出...");
Console.Read();
}
static void Main()
{
autoEvent = new AutoResetEvent(false);
Console.WriteLine("1、主執行緒啟動輔助執行緒...");
Thread t = new Thread(DoWork);
t.Start();
Console.WriteLine("2、主執行緒休眠1秒...");
Thread.Sleep(1000);
Console.WriteLine("4、主執行緒向輔助執行緒發出訊號...");
autoEvent.Set();
}
}
在上面的示例中,建立了一個執行緒,並由 Main 函式啟動該執行緒。 新執行緒使用 方法等待一個事件。 在該事件被執行 Main 函式的主執行緒終止之前,該執行緒一直處於掛起狀態。 一旦該事件終止,輔助執行緒將返回。 執行結果:
5、總結 多執行緒程式設計始終是一個複雜的過程,但只要掌握了基本原理,理清了思路,也並不難,上面僅僅羅列了一些多執行緒程式設計的常見解決措施,這些措施能夠滿足絕大多數情況,但絕不是多執行緒程式設計的全部。