.NETプログラミング研究 第20号 †
.NET Tips †
.NETのマルチスレッドプログラミング その2 †
前回の続きです。
スレッドの状態を取得する †
スレッドの状態を取得するには、Thread.ThreadStateプロパティを使います。スレッドは同時に複数の状態になりえるため、ThreadStateプロパティはThreadState列挙体の値を組み合わせた値になります。ThreadState列挙体のすべてのメンバはヘルプ「ThreadState 列挙体」で説明されています。
下のコードは、スレッドをThread.Startで開始し、Suspendで中断し、Resumeで再開し、Abortで終了させた時にThreadStateがどのように変化するか調べるものです。(これらのメソッドに関しては、後ほど説明します。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| | Class MainClass
Public Shared Sub Main()
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| | class MainClass
{
public static void Main()
{
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 (;;);
}
}
|
出力結果は次のようになります。
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となります。
参考:
スレッドが終了するまで待機する †
あるスレッドが終了するまで現在のスレッドをブロックするには、Thread.Joinメソッドを使います。別のスレッドが確実に終了したところで何らかの処理を行いたいとき(同期処理)などに便利です。(スレッドの同期について詳しくは、別の機会に紹介する予定です。)
次にJoinメソッドを使用した簡単な例を示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| | Public Shared Sub Main()
Dim t As Thread = _
New Thread(New ThreadStart(AddressOf MyThread))
t.Start()
t.Join()
Console.WriteLine("エンターキーで終了します")
Console.ReadLine()
End Sub
Private Shared Sub MyThread()
Thread.Sleep(1000)
Console.WriteLine("終了しました")
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| |
public static void Main()
{
Thread t = new Thread(new ThreadStart(MyThread));
t.Start();
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を呼び出すと、例外がスローされます。
スレッドを中止させる、中止をキャンセルする †
実行中のスレッドを中止させる(強制終了させる)には、そのスレッドのThread.Abortメソッドを呼び出します。Abortメソッドが呼び出されると、そのスレッドの状態はAbortRequestedとなり、その後スレッドが正常に終了するとStoppedになります。
Abortメソッドを呼び出してもすぐにスレッドが中止されるという保障はありません。(セーフポイントに達した時に中止されます。セーフポイントに関してはヘルプ「Thread.Suspend、ガベージ コレクション、およびセーフ ポイント」をご覧ください。)確実にスレッドが中止されるのを待つためには、Thread.Joinメソッドを使用します。
次の例では、別スレッドでPrintIntegerメソッドを実行し、その処理の途中でAbortメソッドによりスレッドを中止し、Joinメソッドによりスレッドが実際に中止されるまで待機しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| |
Public Shared Sub Main()
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| |
public static void Main()
{
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");
が実行されるようになることに注目してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| |
Public Shared Sub Main()
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
Thread.ResetAbort()
End Try
Console.WriteLine("Thread end")
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| |
public static void Main()
{
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)
{
Thread.ResetAbort();
}
Console.WriteLine("Thread end");
}
|
ただしヘルプ(「マネージ スレッド処理の実施」)には、
「他のスレッドを終了させるために Thread.Abort を使用することは避けてください。他のスレッドの Abort を呼び出すことは、そのスレッドの処理がどこまで到達しているかを把握せずに例外をスローするのと同じことになります。」
と書かれていますので、注意が必要です。(別スレッドに対してAbortを呼び出したときの問題点について詳しくは、ヘルプの「Thread.Abort メソッド」をご覧ください。)
スレッドを一時停止させる †
スレッドを一時的に中断するにはThread.Suspendメソッドを、中断されたスレッドを再開するにはThread.Resumeメソッドを呼び出します。
Thread.Abortと同様にThread.Suspendメソッドを呼び出してすぐにスレッドの実行が中断されるわけではなく、セーフポイントに到達するまでは中断されません。
Suspendメソッドが何回呼び出されていたとしても、Resumeメソッドを一回呼び出すだけで再開されます。また、起動していないスレッドや、停止しているスレッドに対してSuspendメソッドを呼び出すと、例外(ThreadStateException)がスローされます。
次の例では、起動したスレッド(t)をSuspendメソッドで中断させた後、しばらくしてからResumeメソッドで再開しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| | Public Shared Sub Main()
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| | public static void Main()
{
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メソッドと違い、呼び出すとスレッドはすぐに停止しますが、停止するスレッド以外のスレッドから呼び出すことはできません。
なお、この方法はスレッドを同期させる方法としては不適切です。スレッドの同期につきましては、別の機会に紹介する予定です。
参考:
コメント †