[C#.NET][Thread] 執行緒的暫停 - WaitHandle.WaitOne 方法

  • 27902
  • 0
  • 2013-08-16

[C#.NET][Thread] 執行緒的暫停 - WaitHandle.WaitOne 方法

WaitHandle.WaitOne方法可以用來暫停執行緒,為了要用UI畫面來呈現效果,下面的程式碼可能會長一點,假設我有以下類別

 


public class Calculator
{
    public string Name { get; set; }
    public long Result { get; set; }
    public int Index { get; set; }
    public bool IsSuspend { get; set; }
    private AutoResetEvent _WaitHandle;
    public AutoResetEvent WaitHandle
    {
        get { return _WaitHandle; }
        set { _WaitHandle = value; }
    }
}

 

在用戶端的程式碼,這個範例裡有使用WaitHandle.WaitAll所以我為了不讓畫面卡死所以又多增加了一條Thread,由runTask方法當主要的子執行緒。

 


object _lock = new object();

bool _isRun = false;//是否執行
int _currentIndex;

List<Calculator> _calculator = null;
WaitHandle[] _waitHandles = null;
private void button1_Click(object sender, EventArgs e)
{
    if (!this._isRun)
    {
        this._isRun = true;
        this.listBox1.Items.Clear();
        ThreadPool.QueueUserWorkItem(new WaitCallback(runTask));
    }
}

主要子執緒負責分配工作給其他執行緒


 


void runTask(Object state)
{
    DateTime dt = DateTime.Now;
    Console.WriteLine("進入主執行緒");
    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 進入主執行緒"); });
    //建立集合
    this._calculator = new List<Calculator>() 
    {
        new Calculator{Result=0,WaitHandle=new AutoResetEvent(false),Name="NO.1",Index=0},
        new Calculator{Result=0,WaitHandle=new AutoResetEvent(false),Name="NO.2",Index=1}
    };

    //建立WaitHandle陣列,因為WaitHandle.WaitAll只收陣列
    this._waitHandles = new WaitHandle[this._calculator.Count];
    for (int i = 0; i < this._calculator.Count; i++)
    {
        this._waitHandles[i] = this._calculator[i].WaitHandle;
    }

    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 呼叫器執行緒" + this._calculator[0].Name); });
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), this._calculator[0]);

    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 呼叫器執行緒" + this._calculator[1].Name); });
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), this._calculator[1]);

    WaitHandle.WaitAll(_waitHandles);
    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 離開主執行緒"); });
    this._isRun = false;
    //解除鎖定
    foreach (Calculator item in this._calculator)
    {
        item.IsSuspend = false;
        //item.Result = 0;
        item.WaitHandle.Set();
    }
}


負責處理運算的方法,Calculator.IsSuspend是暫停旗標,若條件成立則呼叫WaitOne方法,如此一來就能達到執行緒暫停的功能


void DoTask(Object state)
{
    lock (_lock)
    {
        if (!this._isRun)
            return;
        Calculator calculator = (Calculator)state;
        this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 進入子執行緒" + calculator.Name); });

        //目前進入執行緒的索引值
        this._currentIndex = calculator.Index;
        for (long i = 0; i < 10000; i++)
        {
            if (!this._isRun)
                return;

            //收到暫停旗標,呼叫WaitOne方法
            if (calculator.IsSuspend)
                calculator.WaitHandle.WaitOne();

            calculator.Result++;

            //回報UI計算結果
            if (calculator.Name == "NO.1")
                this.BeginInvoke((MethodInvoker)delegate { textBox1.Text = calculator.Result.ToString(); });
            else if (calculator.Name == "NO.2")
                this.BeginInvoke((MethodInvoker)delegate { textBox2.Text = calculator.Result.ToString(); });
            //為了更新UI所以要Sleep
            Thread.Sleep(1);
        }
        calculator.WaitHandle.Set();
        this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 離開子執行緒" + calculator.Name); });
    }
}

 


private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    this._isRun = false;
}

private void btnStop_Click(object sender, EventArgs e)
{
    if (!this._isRun)
        return;
    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 子執行緒停止 NO." + (_currentIndex + 1).ToString()); });
    this._isRun = false;
    //解除鎖定
    foreach (Calculator item in this._calculator)
    {
        item.IsSuspend = false;
        item.WaitHandle.Set();
    }
}

設定Calculator.IsSuspend=true


private void btnSuspend_Click(object sender, EventArgs e)
{
    if (!this._isRun)
        return;
    this._calculator[this._currentIndex].IsSuspend = true;
    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 子執行緒暫停 NO." + (_currentIndex + 1).ToString()); });
}

 

改變Calculator.IsSuspend=false 旗標並解除鎖定:呼叫WaitHandle.Set方法


private void btnContinue_Click(object sender, EventArgs e)
{
    if (!this._isRun)
        return;
    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 子執行緒繼續 NO." + (_currentIndex + 1).ToString()); });
    this._calculator[this._currentIndex].IsSuspend = false;
    this._calculator[this._currentIndex].WaitHandle.Set();
}

 

 

執行結果

image

 


後記:

本篇的重點是WaitOne方法(鎖定)跟Set方法(解除鎖定),瞭解其原理便可輕易的操作執行緒等待,當然這只是一個簡單的範例,並沒有太嚴謹的判斷,你應該針對自己的功能需求加上最嚴謹的條件判斷。

範例下載:WaitHandle_.zip

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo