此樣本示範如何建立輔助線程,並用它與主線程並存執行處理。還將示範如何使一個線程等待另一個線程,並正確地終止線程。有關多執行緒的背景資訊,請參見託管線程處理和使用線程處理(C# 編程指南)。
該樣本建立一個名為 Worker 的類,該類包含輔助線程將執行的方法 DoWork。這實際上是輔助線程的 Main 函數。輔助線程將通過調用此方法來開始執行,並在此方法返回時自動終止。DoWork 方法如下所示:
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
Worker 類包含另一個方法,該方法用於通知 DoWork 它應當返回。此方法名為 RequestStop,如下所示:
public void RequestStop()
{
_shouldStop = true;
}
RequestStop 方法只是將 true 賦給 _shouldStop 資料成員。由於此資料成員由 DoWork 方法來檢查,因此這會間接導致 DoWork 返回,從而終止輔助線程。但是,您應當注意:DoWork 和 RequestStop 將由不同線程執行。DoWork 由輔助線程執行,而 RequestStop 由主線程執行,因此 _shouldStop 資料成員聲明為 volatile,如下所示:
private volatile bool _shouldStop;
volatile 關鍵字用於通知編譯器,將有多個線程訪問 _shouldStop 資料成員,因此它不應當對此成員的狀態做任何最佳化假設。有關更多資訊,請參見volatile(C# 參考)。
通過將 volatile 與 _shouldStop 資料成員一起使用,可以從多個安全執行緒地訪問此成員,而不需要使用正式的線程同步技術,但這僅僅是因為 _shouldStop 是 bool。這意味著只需要執行單個原子操作就能修改 _shouldStop。但是,如果此資料成員是類、結構或數組,那麼,從多個線程訪問它可能會導致間歇的資料損毀。假設有一個更改數組中的值的線程。Windows 會定期中斷線程,以便允許其他線程執行。因此,此線程會在分配某些數組元素之後和分配其他元素之前被停止。由於數組現在有了一個程式員從不想要的狀態,因此,讀取此數組的另一個線程可能會失敗。
在實際建立輔助線程之前,Main 函數會建立一個 Worker 對象和 Thread 的一個執行個體。線程對象被配置為:通過將對 Worker.DoWork 方法的引用傳遞給 Thread 建構函式,來將該方法用作進入點,如下所示:
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
此時,儘管輔助線程對象已存在並已配置,但尚未建立實際的輔助線程。只有當 Main 調用 Start 方法後,才會建立實際的輔助線程:
workerThread.Start();
此時,系統將啟動輔助線程的執行,但這是在與主線程非同步執行的。這意味著 Main 函數將在輔助線程進行初始化的同時繼續執行代碼。為了確保 Main 函數不會嘗試在輔助線程有機會執行之前將它終止,Main 函數將一直迴圈,直到輔助線程對象的 IsAlive 屬性設定為 true:
while (!workerThread.IsAlive);
下一步,通過調用 Sleep 來將主線程停止片刻。這確保了輔助線程的 DoWork 函數在 Main 函數執行其他任何命令之前,在 DoWork 方法內部執行若干次迴圈:
Thread.Sleep(1);
在 1 毫秒之後,Main 將通知輔助線程對象,它應當使用 Worker.RequestStop 方法(前面已介紹)自行終止:
workerObject.RequestStop();
還可以通過調用 Abort 從另一個線程中終止某個線程。這將強行終止受影響的線程,即使該線程尚未完成其任務,並且未提供清理資源的機會。此樣本中顯示的技術是首選方法。
最後,Main 函數對輔助線程對象調用 Join 方法。此方法導致當前線程阻塞或等待,直到對象所表示的線程終止。因此,直到輔助線程返回後,Join 才會返回,然後自行終止:
workerThread.Join();
此時,只有執行 Main 的主線程還存在。它會顯示一條最終訊息,然後返回,從而使主線程也終止。
下面是完整的樣本。
樣本:
using System;
using System.Threading;
public class Worker
{
// This method will be called when the thread is started.
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Volatile is used as hint to the compiler that this data
// member will be accessed by multiple threads.
private volatile bool _shouldStop;
}
public class WorkerThreadExample
{
static void Main()
{
// Create the thread object. This does not start the thread.
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
// Start the worker thread.
workerThread.Start();
Console.WriteLine("main thread: Starting worker thread");
// Loop until worker thread activates.
while (!workerThread.IsAlive);
// Put the main thread to sleep for 1 millisecond to
// allow the worker thread to do some work:
Thread.Sleep(1);
// Request that the worker thread stop itself:
workerObject.RequestStop();
// Use the Join method to block the current thread
// until the object's thread terminates.
workerThread.Join();
Console.WriteLine("main thread: Worker thread has terminated.");
}
}
輸出如下:
main thread: starting worker thread
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: working
worker thread: terminating gracefully
main thread: worker thread has terminated