.NETプログラミング研究 第19号 †
.NET Tips †
.NETのマルチスレッドプログラミング †
.NETのマルチスレッドプログラミングについて、今号から数回にわたって説明していきます。
新しいスレッドを作成し、実行する †
まずは、マルチスレッドプログラミングのはじめの一歩である、新しいスレッドを作成し、実行する方法を紹介します。
マルチスレッドを使用することにより、より効率的なアプリケーションを作成できるケースが数多くあります。例えば、長く時間のかかる処理の間ユーザーインターフェイスを有効にし、ユーザーの入力に瞬時に対応する(処理中のキャンセルボタンへの対応など)ことが出来ますし、複数のURLから同時にファイルをダウンロードするようなアプリケーションを作成することも出来ます。
(スレッドとは何か、マルチスレッドの利点と欠点などに関して詳しくは、ヘルプの「スレッドおよびスレッド処理」や「マルチスレッド アプリケーション」等をご覧ください。)
マルチスレッドはうまく使えば非常に便利ですが、マルチスレッドアプリケーションの作成は非常に難しく、正しく理解せずに使用することは無謀であり、絶対にやめるべきです。残念ながらこの号で私が紹介する事柄は、マルチスレッドを理解するために必要な知識の半分にも及びませんので、マルチスレッドアプリケーションをはじめて作るという方は更なる勉強が必要になると思ってください。(このメールマガジンでおいおい紹介していくつもりです。)
.NET FrameworkのSystem.Threading名前空間にはマルチスレッドをサポートするいくつかのクラスが用意されています。まずは、その内最も基本的なThreadクラスによる新しいスレッドの作成と、開始の方法について説明します。
まずは、マルチスレッドを用いない次のようなコンソールアプリを見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| | Class MainClass
Public Shared Sub Main()
Console.WriteLine("スタート")
SpendLongTime()
Console.WriteLine("待機中...")
Console.ReadLine()
End Sub
Private Shared Sub SpendLongTime()
System.Threading.Thread.Sleep(10000)
Console.WriteLine("終わりました")
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
| | class MainClass
{
public static void Main()
{
Console.WriteLine("スタート");
SpendLongTime();
Console.WriteLine("待機中...");
Console.ReadLine();
}
private static void SpendLongTime()
{
System.Threading.Thread.Sleep(10000);
Console.WriteLine("終わりました");
}
}
|
このアプリはまず「スタート」と出力した後、SpendLongTimeメソッドを実行し、SpendLongTimeメソッドがすべて処理されてから、「待機中...」と表示されます(つまり出力の順番としては、「スタート」、(10秒待機)、「終わりました」、「待機中...」となります)。
さて、いよいよマルチスレッドの例として、上記コードのSpendLongTimeメソッドを別のスレッドで実行するようにしてみます。まず、SpendLongTimeメソッドを参照するThreadStartデリゲートオブジェクトを作成し、これを使ってThreadオブジェクトを作成します。そして、スレッドの実行を開始させるために、Thread.Startメソッドを呼び出します。ThreadStartデリゲートには引数も戻り値もありませんので、引数、戻り値のあるメソッドでは不可です。
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
| | Class MainClass
Public Shared Sub Main()
Console.WriteLine("スタート")
Dim t As New System.Threading.Thread( _
New System.Threading.ThreadStart( _
AddressOf SpendLongTime))
t.Start()
Console.WriteLine("待機中...")
Console.ReadLine()
End Sub
Private Shared Sub SpendLongTime()
System.Threading.Thread.Sleep(10000)
Console.WriteLine("終わりました")
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
| | class MainClass
{
public static void Main()
{
Console.WriteLine("スタート");
System.Threading.Thread t =
new System.Threading.Thread(
new System.Threading.ThreadStart(SpendLongTime));
t.Start();
Console.WriteLine("待機中...");
Console.ReadLine();
}
private static void SpendLongTime()
{
System.Threading.Thread.Sleep(10000);
Console.WriteLine("終わりました");
}
}
|
このアプリを実行すると、まず「スタート」と出力され、その後すぐに「待機中...」と表示し、10秒後に「終わりました」と表示されます。ここでは先のマルチスレッドを用いない例と違い、SpendLongTimeが別スレッドで開始されたため、メインスレッドがSpendLongTimeの処理でブロックされることなく、この2つのスレッドは平行して同時に(シングルプロセッサでは本当はそうではないが)実行されます。
フォアグラウンドスレッドとバックグラウンドスレッドの違い †
スレッドには、フォアグラウンドスレッドとバックグランドスレッドの2種類があります。プロセス内のフォアグラウンドスレッドがすべて終了した時にそのプロセスは終了しますが(逆に言えば、すべてのフォアグラウンドスレッドが終了しなければプロセスは終了しない)、プロセス終了時に実行中のすべてのバックグランドスレッドは終了させられます(Abortが呼び出されます)。
Threadオブジェクトが新しく作成、起動されたとき、そのスレッドはデフォルトでフォアグラウンドスレッドとなります。また、スレッドプールが使用するスレッドや、アンマネージコードからマネージ実行環境に入るスレッドは、バックグラウンドスレッドとなります。
スレッドをフォアグラウンドスレッドまたはバックグラウンドスレッドにするには、Thread.IsBackgroundプロパティをfalseまたはtrueにします。
具体的な例を見てみましょう。まずはフォアグラウンドスレッドの例です。MainメソッドはMyMethod1メソッドを別スレッドで実行し、エンターキーが押されるまで待機します。MyMethod1メソッドはフォアグラウンドスレッドで実行しているため、MyMethod1メソッドが終了しなければ、エンターキーが押されてもプロセスは終了しません。
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
| |
Class MainClass
Public Shared Sub Main()
Dim t1 As New Thread(New ThreadStart(AddressOf MyMethod1))
t1.IsBackground = False
t1.Start()
Console.ReadLine()
Console.WriteLine("メインスレッド終了")
End Sub
Public Shared Sub MyMethod1()
Console.WriteLine("スレッド開始")
Thread.Sleep(10000)
Console.WriteLine("スレッド終了")
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
| |
class MainClass
{
public static void Main()
{
Thread t1 = new Thread(new ThreadStart(MyMethod1));
t1.IsBackground = false;
t1.Start();
Console.ReadLine();
Console.WriteLine("メインスレッド終了");
}
public static void MyMethod1()
{
Console.WriteLine("スレッド開始");
Thread.Sleep(10000);
Console.WriteLine("スレッド終了");
}
}
|
次はバックグラウンドスレッドの例です。作成したスレッドがバックグラウンドスレッドである点を除いて前のコードと変わりありません。今度はMyMethod1メソッドをバックグラウンドスレッドで実行しているため、MyMethod1メソッドが終了する前にエンターキーを押してもすぐにプロセスが終了します。
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
| |
Class MainClass
Public Shared Sub Main()
Dim t1 As New Thread(New ThreadStart(AddressOf MyMethod1))
t1.IsBackground = True
t1.Start()
Console.ReadLine()
Console.WriteLine("メインスレッド終了")
End Sub
Public Shared Sub MyMethod1()
Console.WriteLine("スレッド開始")
Thread.Sleep(10000)
Console.WriteLine("スレッド終了")
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
| |
class MainClass
{
public static void Main()
{
Thread t1 = new Thread(new ThreadStart(MyMethod1));
t1.IsBackground = false;
t1.Start();
Console.ReadLine();
Console.WriteLine("メインスレッド終了");
}
public static void MyMethod1()
{
Console.WriteLine("スレッド開始");
Thread.Sleep(10000);
Console.WriteLine("スレッド終了");
}
}
|
スレッドに優先順位をつける †
スレッドの優先順位はThread.Priorityプロパティによって変更することが出来ます。優先順位はThreadPriority列挙体で指定し、優勢順位が高い方から、Highest, AboveNormal, Normal, BelowNormal, Lowestの5段階があります。優先順位はすべてのスレッドにつけられており、Threadクラスで作成されたスレッドのデフォルトはNormalとなります。
この優先順位というのは、OSによるスレッドのスケジューリングの優先順位のことです。よってその動作はOSによるということになりそうですが、一般的には優先順位のより高いスレッドの処理にプロセッサがより多くの時間を割く(より多くのプロセッサタイムスライスを割り当てる)ことになります。その結果、優先順位の低いスレッドはより高いスレッドがすべて終了する(あるいは待機状態になる)まで実行されないということにもなります。(詳しくはヘルプ「スレッドのスケジューリング」をご覧ください。)
このスレッドの優先順位を利用することにより、例えば、長く時間のかかる処理の間ユーザーからの入力を待っているような場合、ユーザーからの入力待ちのためのスレッドの優先順位を高くして、その反応を良くするといったことができます。
優先順位の違いにより、スレッドがどのように実行されるか具体例で見てみましょう。まずは優先順位をつけずに(Normalのまま)2つのスレッドを作成し、実行してみます。
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
| |
Public Shared Sub Main()
Dim t1 As New Thread(New ThreadStart(AddressOf MyThread))
t1.Name = "1"
Dim t2 As New Thread(New ThreadStart(AddressOf MyThread))
t2.Name = "2"
t1.Start()
Dim i As Integer
For i = 0 To 99
Console.Write("")
Next i
t2.Start()
t1.Join()
t2.Join()
End Sub
Private Shared Sub MyThread()
Dim i As Integer
For i = 0 To 199
Console.Write(Thread.CurrentThread.Name)
Next i
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
| |
public static void Main()
{
Thread t1 = new Thread(new ThreadStart(MyThread));
t1.Name = "1";
Thread t2 = new Thread(new ThreadStart(MyThread));
t2.Name = "2";
t1.Start();
for (int i = 0; i < 100; i++)
Console.Write("");
t2.Start();
t1.Join();
t2.Join();
}
private static void MyThread()
{
for (int i = 0; i < 200; i++)
{
Console.Write(Thread.CurrentThread.Name);
}
}
|
結果は例えば次のようになります(スレッド1が"1"を、スレッド2が"2"を出力する)。2つのスレッドはバラバラに実行されます。(もう少しループを長くすると分かりやすくなります。)
111111111111111111111111111111111111111111111111111111111111111111111
111111111111222222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222222222222222111
111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111122222222
次に、2番目に実行するスレッドに最高の優先順位であるHighestを指定して、同様に実行してみます。
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 t1 As New Thread(New ThreadStart(AddressOf MyThread))
t1.Name = "1"
Dim t2 As New Thread(New ThreadStart(AddressOf MyThread))
t2.Name = "2"
t2.Priority = ThreadPriority.Highest
t1.Start()
Dim i As Integer
For i = 0 To 99
Console.Write("")
Next i
t2.Start()
t1.Join()
t2.Join()
End Sub
Private Shared Sub MyThread()
Dim i As Integer
For i = 0 To 199
Console.Write(Thread.CurrentThread.Name)
Next i
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
| |
public static void Main()
{
Thread t1 = new Thread(new ThreadStart(MyThread));
t1.Name = "1";
Thread t2 = new Thread(new ThreadStart(MyThread));
t2.Name = "2";
t2.Priority = ThreadPriority.Highest;
t1.Start();
for (int i = 0; i < 100; i++)
Console.Write("");
t2.Start();
t1.Join();
t2.Join();
}
private static void MyThread()
{
for (int i = 0; i < 200; i++)
{
Console.Write(Thread.CurrentThread.Name);
}
}
|
結果は例えば次のようになります。
111111111111111111111111111111111111111111111111111111111111111122222
222222222222222222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222222111111111111
111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111
はじめに優先順位がNormalのスレッド"1"が開始されますが、より高い優先順位(Highest)のスレッド"2"が開始されるとそちらが優先され、スレッド"2"が終わってから再びスレッド"1"に実行が移ります。
コメント †