方法 : スレッドを作成および終了する (C# プログラミング ガイド)
更新 : 2007 年 11 月
以下の例では、補助スレッドつまりワーカー スレッドを作成および使用して、プライマリ スレッドとの並行処理を実行する方法を示します。また、1 つのスレッドに別のスレッドを待機させる方法とスレッドを適切に終了する方法も示します。マルチスレッド処理の背景情報については、「マネージ スレッド処理」および「スレッド処理の使用 (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 メソッドは、_shouldStop データ メンバに true を設定するだけです。このデータ メンバは、DoWork メソッドによってチェックされるため、この処理には、DoWork から制御を戻し、それによってワーカー スレッドを終了させるという間接的な効果があります。ただし、DoWork と RequestStop は別々のスレッドによって実行されることに注意する必要があります。DoWork は、ワーカー スレッドによって実行され、RequestStop は、プライマリ スレッドによって実行されるため、_shouldStop データ メンバは、次のように volatile と宣言されます。
private volatile bool _shouldStop;
volatile キーワードは、複数のスレッドが _shouldStop データ メンバにアクセスするため、このメンバの状態についての最適化を想定しないようコンパイラに警告します。詳細については、「volatile (C# リファレンス)」を参照してください。
_shouldStop データ メンバで volatile を使用することにより、正規のスレッド同期手法を使わずに複数のスレッドからこのメンバに安全にアクセスできますが、これは、_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 を実行しているプライマリ スレッドのみが存在します。Main 関数は 1 つの最終メッセージを表示した後、制御を戻し、プライマリ スレッドも終了します。
完全な例を次に示します。
使用例
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