[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();
}
執行結果
後記:
本篇的重點是WaitOne方法(鎖定)跟Set方法(解除鎖定),瞭解其原理便可輕易的操作執行緒等待,當然這只是一個簡單的範例,並沒有太嚴謹的判斷,你應該針對自己的功能需求加上最嚴謹的條件判斷。
範例下載:WaitHandle_.zip
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET