1. 程式人生 > >C# 執行緒(Thread)的常見解決方案

C# 執行緒(Thread)的常見解決方案

前言:多執行緒程式設計是應用程式開發中一個非常重要的部分,這裡總結一些常見的執行緒(Thread)解決方案。注意:這裡僅涉及Thread的常見解決方案,CLR 4.0以後的Task這裡不做描述。

目錄
1、使用BackgroundWorker元件2、執行緒計時器3、執行緒池4、執行緒同步5、總結一、使用BackgroundWorker元件
 可以通過程式設計方式建立 物件,在WinForm中也可以將它從“工具箱”“元件”選項卡中拖到窗體上。
我們看一下它的事件:
1、 事件呼叫要執行的後臺操作;WorkerReportsProgress屬性要設為true);我們看一個簡單的示例:
建立Windows窗體應用程式。新增一個名為resultLabel
Label控制元件並新增兩個名為 startAsyncButtoncancelAsyncButton 的 控制元件。
建立這兩個按鈕的 Click 事件處理程式。 從工具箱中的“元件”選項卡中,新增命名為 backgroundWorker1BackgroundWorker 元件。
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!";
            }
        }

    }
}
可以看到無論是反饋操作進度,還是操作完成觸發事件,亦或是請求取消後臺操作,都非常方便,當涉及單個執行緒的操作時,優先推薦使用 ,而不是Thread。、執行緒計時器類可用於定期在單獨的執行緒上執行任務。例如,可以使用執行緒計時器來檢查資料庫的狀態和完整性,或備份重要檔案。
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();
            }
        }
    }
}
、執行緒池“執行緒池”是可以用來在後臺執行多個任務的執行緒集合。 這使主執行緒可以自由地非同步執行其他任務。執行緒池通常用於伺服器應用程式。每個傳入請求都將分配給執行緒池中的一個執行緒,因此可以非同步處理請求,而不會佔用主執行緒,也不會延遲後續請求的處理。一旦池中的某個執行緒完成任務,它將返回到等待執行緒佇列中,等待被再次使用。 這種重用使應用程式可以避免為每個任務建立新執行緒的開銷。執行緒池通常具有最大執行緒數限制。 如果所有執行緒都繁忙,則額外的任務將放入佇列中,直到有執行緒可用時才能夠得到處理。雖然可以實現自己的執行緒池,但是強烈推薦通過 類使用 .NET Framework 提供的執行緒池。
        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、總結    多執行緒程式設計始終是一個複雜的過程,但只要掌握了基本原理,理清了思路,也並不難,上面僅僅羅列了一些多執行緒程式設計的常見解決措施,這些措施能夠滿足絕大多數情況,但絕不是多執行緒程式設計的全部。