• 追加された行はこの色です。
  • 削除された行はこの色です。
#title(.NETプログラミング研究 第20号)

#navi(.NETプログラミング研究)

#contents

*.NETプログラミング研究 第20号 [#cba6c649]

**.NET Tips [#h0dba22c]

**.NETのマルチスレッドプログラミング その2 [#g5b20a9c]

#column(注意){{
この記事の最新版は「[[.NETのマルチスレッドプログラミング>http://dobon.net/vb/dotnet/programing/multithread.html]]」で公開しています。
この記事の最新版は「[[.NETのマルチスレッドプログラミング>https://dobon.net/vb/dotnet/programing/multithread.html]]」で公開しています。
}}

前回の続きです。

***スレッドの状態を取得する [#ib1814ba]

スレッドの状態を取得するには、Thread.ThreadStateプロパティを使います。スレッドは同時に複数の状態になりえるため、ThreadStateプロパティはThreadState列挙体の値を組み合わせた値になります。ThreadState列挙体のすべてのメンバはヘルプ「ThreadState 列挙体」で説明されています。

-[[ThreadState 列挙体>http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemthreadingthreadstateclasstopic.asp]]

下のコードは、スレッドをThread.Startで開始し、Suspendで中断し、Resumeで再開し、Abortで終了させた時にThreadStateがどのように変化するか調べるものです。(これらのメソッドに関しては、後ほど説明します。)

#code(vbnet){{
Class MainClass
    'エントリポイント
    Public Shared Sub Main()
        'Threadオブジェクトを作成する
        Dim t1 As System.Threading.Thread = _
            New System.Threading.Thread( _
                New System.Threading.ThreadStart(AddressOf MyMethod1))

        'スレッドの状態を表示
        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())

        Console.WriteLine(">Start")
        'スレッドを開始する
        t1.Start()

        System.Threading.Thread.Sleep(50)
        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())

        Console.WriteLine(">Suspend")
        'スレッドを中断
        t1.Suspend()

        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())
        System.Threading.Thread.Sleep(50)
        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())

        Console.WriteLine(">Resume")
        'スレッドの再開
        t1.Resume()

        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())
        System.Threading.Thread.Sleep(50)
        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())

        Console.WriteLine(">Abort")
        'スレッドの停止
        t1.Abort()

        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())

        'スレッドが終了するまで待つ
        t1.Join()

        Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString())

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド
    Private Shared Sub MyMethod1()
        '無限ループ
        While True
        End While
    End Sub
End Class
}}

#code(csharp){{
class MainClass
{
	//エントリポイント
	public static void Main()
	{
		//Threadオブジェクトを作成する
		System.Threading.Thread t1 = 
			new System.Threading.Thread(
				new System.Threading.ThreadStart(MyMethod1));

		//スレッドの状態を表示
		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());

		Console.WriteLine(">Start");
		//スレッドを開始する
		t1.Start();

		System.Threading.Thread.Sleep(50);
		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());

		Console.WriteLine(">Suspend");
		//スレッドを中断
		t1.Suspend();

		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());
		System.Threading.Thread.Sleep(50);
		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());

		Console.WriteLine(">Resume");
		//スレッドの再開
		t1.Resume();

		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());
		System.Threading.Thread.Sleep(50);
		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());

		Console.WriteLine(">Abort");
		//スレッドの停止
		t1.Abort();

		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());

		//スレッドが終了するまで待つ
		t1.Join();

		Console.WriteLine("ThreadState:{0}", t1.ThreadState.ToString());

		Console.ReadLine();
	}

	//別スレッドで実行するメソッド
	private static void MyMethod1()
	{
		//無限ループ
		for (;;);
	}
}
}}

出力結果は次のようになります。

#pre{{
ThreadState:Unstarted
>Start
ThreadState:Running
>Suspend
ThreadState:SuspendRequested
ThreadState:Suspended
>Resume
ThreadState:Running
ThreadState:Running
>Abort
ThreadState:AbortRequested
ThreadState:Stopped
}}

スレッド開始前はUnstarted、開始後はRunning、Suspendメソッドで一時停止するとSuspendRequestedからSuspendedへ、Abortメソッドを呼び出した直後はAbortRequestedで、スレッドが終了するとStoppedとなります。

上記の例ではそうなりませんでしたが、スレッドが同時に複数の状態になることがありますので、ThreadStateプロパティでスレッドの状態を調べるには、AND演算子を使って、例えば、

 if ((t1.ThreadState & ThreadState.Stopped) > 0)

のようにします。

また、スレッドが実行中かどうか調べるだけであれば、IsAliveプロパティを使うこともできます。スレッドの状態がUnstarted、Stopped、WaitSleepJoinの時はIsAliveがfalseとなります。

参考:

-[[スレッド状態>http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconthreadactivitystates.asp]]
-[[スレッドの状態>http://www.microsoft.com/japan/msdn/library/ja/vbcn7/html/vaconthreadstates.asp]]

***スレッドが終了するまで待機する [#k2aaadca]

あるスレッドが終了するまで現在のスレッドをブロックするには、Thread.Joinメソッドを使います。別のスレッドが確実に終了したところで何らかの処理を行いたいとき(同期処理)などに便利です。(スレッドの同期について詳しくは、別の機会に紹介する予定です。)

次にJoinメソッドを使用した簡単な例を示します。

#code(vbnet){{
'エントリポイント
Public Shared Sub Main()
    'スレッドの作成と開始
    Dim t As Thread = _
        New Thread(New ThreadStart(AddressOf MyThread))
    t.Start()

    'スレッドtが終了するまでブロックする
    t.Join()

    Console.WriteLine("エンターキーで終了します")
    Console.ReadLine()
End Sub

Private Shared Sub MyThread()
    '何らかの処理があるものとする
    Thread.Sleep(1000)
    Console.WriteLine("終了しました")
End Sub
}}

#code(csharp){{
//using System.Threading;
//が宣言されているものとする

//エントリポイント
public static void Main()
{
	//スレッドの作成と開始
	Thread t = new Thread(new ThreadStart(MyThread));
	t.Start();

	//スレッドtが終了するまでブロックする
	t.Join();

	Console.WriteLine("エンターキーで終了します");
	Console.ReadLine();
}

private static void MyThread()
{
	//何らかの処理があるものとする
	Thread.Sleep(1000);
	Console.WriteLine("終了しました");
}
}}

上記の例のようにJoinメソッドを引数なしで呼び出すと、スレッドが終了するまで無制限にブロックされます。JoinメソッドにInt32型かTimeSpan型を指定することにより、ブロックする最大時間を指定できます。この時、Joinメソッドは、スレッドが終了した時はtrueを、指定した時間が経過してもスレッドが終了しなかった時はfalseを返します。

Joinメソッドにより待機しているスレッドの状態は、WaitSleepJoinとなります。また、Unstarted状態のスレッドでJoinを呼び出すと、例外がスローされます。

***スレッドを中止させる、中止をキャンセルする [#yc4fe716]

実行中のスレッドを中止させる(強制終了させる)には、そのスレッドのThread.Abortメソッドを呼び出します。Abortメソッドが呼び出されると、そのスレッドの状態はAbortRequestedとなり、その後スレッドが正常に終了するとStoppedになります。

Abortメソッドを呼び出してもすぐにスレッドが中止されるという保障はありません。(セーフポイントに達した時に中止されます。セーフポイントに関してはヘルプ「Thread.Suspend、ガベージ コレクション、およびセーフ ポイント」をご覧ください。)確実にスレッドが中止されるのを待つためには、Thread.Joinメソッドを使用します。

-[[Thread.Suspend、ガベージ コレクション、およびセーフ ポイント>http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconthreadsuspendgarbagecollectionsafepoints.asp]]

次の例では、別スレッドでPrintIntegerメソッドを実行し、その処理の途中でAbortメソッドによりスレッドを中止し、Joinメソッドによりスレッドが実際に中止されるまで待機しています。

#code(vbnet){{
'Imports System.Threading
'が宣言されているものとする

'エントリポイント
Public Shared Sub Main()
    'Threadオブジェクトを作成する
    Dim t As Thread = _
        New Thread(New ThreadStart(AddressOf PrintInteger))
    'スレッドを開始する
    t.Start()

    'しばらく待機する
    Thread.Sleep(1000)

    'スレッドを中断する
    t.Abort()

    'スレッドが終了するまで待機する
    t.Join()

    Console.ReadLine()
End Sub

'別スレッドで実行するメソッド
Private Shared Sub PrintInteger()
    '無限ループ
    While True
        Dim i As Integer
        For i = 0 To 9
            Console.Write(i)
        Next
        '到達できないコード
        Console.WriteLine("Thread end")
    End While
End Sub
}}

#code(csharp){{
//using System.Threading;
//が宣言されているものとする

//エントリポイント
public static void Main()
{
	//Threadオブジェクトを作成する
	Thread t = new Thread(new ThreadStart(PrintInteger));
	//スレッドを開始する
	t.Start();

	//しばらく待機する
	Thread.Sleep(1000);

	//スレッドを中断する
	t.Abort();

	//スレッドが終了するまで待機する
	t.Join();

	Console.ReadLine();
}

//別スレッドで実行するメソッド
private static void PrintInteger()
{
	//無限ループ
	for (;;)
		for (int i = 0; i < 10; i++)
			Console.Write(i);

	//到達できないコード
	Console.WriteLine("Thread end");
}
}}

Abortメソッドを呼び出すと、スレッドでThreadAbortExceptionがスローされます。ThreadAbortExceptionをキャッチし、Thread.ResetAbortメソッドを呼び出すことにより、Abortメソッドにより行われた中止の要求をキャンセルすることが出来ます。なお、ResetAbortメソッドはThreadAbortExceptionが発生したスレッドからのみ呼び出すことができます。

次の例では上記のコードに追加して、ThreadAbortExceptionをキャッチし、ResetAbortメソッドにより、中止をキャンセルしています。その結果、その後の
Console.WriteLine("Thread end");
が実行されるようになることに注目してください。

#code(vbnet){{
'Imports System.Threading
'が宣言されているものとする

'エントリポイント
Public Shared Sub Main()
    'Threadオブジェクトを作成する
    Dim t As Thread = _
        New Thread(New ThreadStart(AddressOf PrintInteger))
    'スレッドを開始する
    t.Start()

    'しばらく待機する
    Thread.Sleep(1000)

    'スレッドを中断する
    t.Abort()

    'スレッドが終了するまで待機する
    t.Join()

    Console.ReadLine()
End Sub

'別スレッドで実行するメソッド
Private Shared Sub PrintInteger()
    Try
        '無限ループ
        While True
            Dim i As Integer
            For i = 0 To 9
                Console.Write(i)
            Next
        End While
    Catch ex As ThreadAbortException
        'Abortをキャンセルする
        Thread.ResetAbort()
    End Try

    'Thread.ResetAbortにより、以下が実行されるようになる
    Console.WriteLine("Thread end")
End Sub
}}

#code(csharp){{
//using System.Threading;
//が宣言されているものとする

//エントリポイント
public static void Main()
{
	//Threadオブジェクトを作成する
	Thread t = new Thread(new ThreadStart(PrintInteger));
	//スレッドを開始する
	t.Start();

	//しばらく待機する
	Thread.Sleep(1000);

	//スレッドを中断する
	t.Abort();

	//スレッドが終了するまで待機する
	t.Join();

	Console.ReadLine();
}

//別スレッドで実行するメソッド
private static void PrintInteger()
{
	try
	{
		//無限ループ
		for (;;)
			for (int i = 0; i < 10; i++)
				Console.Write(i);
	}
	catch (ThreadAbortException)
	{
		//Abortをキャンセルする
		Thread.ResetAbort();
	}

	//Thread.ResetAbortにより、以下が実行されるようになる
	Console.WriteLine("Thread end");
}
}}

ただしヘルプ(「マネージ スレッド処理の実施」)には、
「他のスレッドを終了させるために Thread.Abort を使用することは避けてください。他のスレッドの Abort を呼び出すことは、そのスレッドの処理がどこまで到達しているかを把握せずに例外をスローするのと同じことになります。」
と書かれていますので、注意が必要です。(別スレッドに対してAbortを呼び出したときの問題点について詳しくは、ヘルプの「Thread.Abort メソッド」をご覧ください。)

-[[Thread.Abort メソッド>http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemthreadingthreadclassaborttopic.asp]]

***スレッドを一時停止させる [#t4b5cb0a]

スレッドを一時的に中断するにはThread.Suspendメソッドを、中断されたスレッドを再開するにはThread.Resumeメソッドを呼び出します。

Thread.Abortと同様にThread.Suspendメソッドを呼び出してすぐにスレッドの実行が中断されるわけではなく、セーフポイントに到達するまでは中断されません。

Suspendメソッドが何回呼び出されていたとしても、Resumeメソッドを一回呼び出すだけで再開されます。また、起動していないスレッドや、停止しているスレッドに対してSuspendメソッドを呼び出すと、例外(ThreadStateException)がスローされます。

次の例では、起動したスレッド(t)をSuspendメソッドで中断させた後、しばらくしてからResumeメソッドで再開しています。

#code(vbnet){{
'エントリポイント
Public Shared Sub Main()
    'PrintIntegerメソッドを実行するための
    'Threadオブジェクトを作成する
    Dim t As System.Threading.Thread = _
        New System.Threading.Thread( _
            New System.Threading.ThreadStart(AddressOf PrintInteger))
    'スレッドを開始する
    t.Start()

    'しばらく待機する
    System.Threading.Thread.Sleep(100)

    Console.Write("<< Suspend >>")
    'スレッドを中断する
    t.Suspend()

    '再びしばらく待機する
    System.Threading.Thread.Sleep(1000)

    '中断したスレッドを再開する
    t.Resume()
    Console.Write("<< Resumed >>")

    'みたびしばらく待機する
    System.Threading.Thread.Sleep(100)

    Console.Write("<< Abort >>")
    'スレッドを終了する
    t.Abort()

    Console.ReadLine()
End Sub

'別スレッドで実行するメソッド
Private Shared Sub PrintInteger()
    '無限ループ
    While True
        Dim i As Integer
        For i = 0 To 9
            Console.Write(i)
        Next
    End While
End Sub
}}

#code(csharp){{
//エントリポイント
public static void Main()
{
	//PrintIntegerメソッドを実行するための
	//Threadオブジェクトを作成する
	System.Threading.Thread t = 
		new System.Threading.Thread(
			new System.Threading.ThreadStart(PrintInteger));
	//スレッドを開始する
	t.Start();

	//しばらく待機する
	System.Threading.Thread.Sleep(100);

	Console.Write("<< Suspend >>");
	//スレッドを中断する
	t.Suspend();

	//再びしばらく待機する
	System.Threading.Thread.Sleep(1000);

	//中断したスレッドを再開する
	t.Resume();
	Console.Write("<< Resumed >>");

	//みたびしばらく待機する
	System.Threading.Thread.Sleep(100);

	Console.Write("<< Abort >>");
	//スレッドを終了する
	t.Abort();

	Console.ReadLine();
}

//別スレッドで実行するメソッド
private static void PrintInteger()
{
	//無限ループ
	for (;;)
		for (int i = 0; i < 10; i++)
			Console.Write(i);
}
}}

(補足:
上記のコードでは、

 t.Suspend();

と

 t.Resume();

の間にConsole.Writeを入れると、そこでフリーズしてしまいました。)

スレッドを一時停止させる方法として、Thread.Sleepメソッドを使うこともできます。SleepメソッドはSuspendメソッドと違い、呼び出すとスレッドはすぐに停止しますが、停止するスレッド以外のスレッドから呼び出すことはできません。

なお、この方法はスレッドを同期させる方法としては不適切です。スレッドの同期につきましては、別の機会に紹介する予定です。

参考:

-[[スレッドの一時中断と再開>http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconworkingwiththreads.asp]]

**コメント [#y3208879]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2003-11-04 (火) 06:00:00,DOBON!,2010-03-21 (日) 01:04:22,DOBON!)

[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]