.NETプログラミング研究 第19号

.NET Tips

.NETのマルチスレッドプログラミング

注意

この記事の最新版は「.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メソッドを実行
        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メソッドを実行
        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("スタート")
 
        'SpendLongTimeメソッドを実行するための
        'Threadオブジェクトを作成する
        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("スタート");
 
        //SpendLongTimeメソッドを実行するための
        //Threadオブジェクトを作成する
        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
'Imports System.Threading
'が宣言されているものとする
 
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("スレッド開始")
 
        '10秒待機する
        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
//using System.Threading;
//が宣言されているものとする
 
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("スレッド開始");
 
        //10秒待機する
        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
'Imports System.Threading
'が宣言されているものとする
 
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("スレッド開始")
 
        '10秒待機する
        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
//using System.Threading;
//が宣言されているものとする
 
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("スレッド開始");
 
        //10秒待機する
        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
'Imports System.Threading
'が宣言されているものとする
 
'エントリポイント
Public Shared Sub Main()
    '2つのThreadオブジェクトを作成する
    Dim t1 As New Thread(New ThreadStart(AddressOf MyThread))
    t1.Name = "1"
    Dim t2 As New Thread(New ThreadStart(AddressOf MyThread))
    t2.Name = "2"
 
    '1つのスレッドを開始
    t1.Start()
 
    'しばらく待機の後2つ目のスレッドを開始
    Dim i As Integer
    For i = 0 To 99
        Console.Write("")
    Next i
    t2.Start()
 
    '2つのスレッドが終了するまで待つ
    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
//using System.Threading;
//が宣言されているものとする
 
//エントリポイント
public static void Main()
{
    //2つのThreadオブジェクトを作成する
    Thread t1 = new Thread(new ThreadStart(MyThread));
    t1.Name = "1";
    Thread t2 = new Thread(new ThreadStart(MyThread));
    t2.Name = "2";
 
    //1つのスレッドを開始
    t1.Start();
 
    //しばらく待機の後2つ目のスレッドを開始
    for (int i = 0; i < 100; i++)
        Console.Write("");
    t2.Start();
 
    //2つのスレッドが終了するまで待つ
    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
'Imports System.Threading
'が宣言されているものとする
 
'エントリポイント
Public Shared Sub Main()
    '2つのThreadオブジェクトを作成する
    Dim t1 As New Thread(New ThreadStart(AddressOf MyThread))
    t1.Name = "1"
    Dim t2 As New Thread(New ThreadStart(AddressOf MyThread))
    t2.Name = "2"
    '2つ目のスレッドの優先順位を最高にする
    t2.Priority = ThreadPriority.Highest
 
    '1つのスレッドを開始
    t1.Start()
 
    'しばらく待機の後2つ目のスレッドを開始
    Dim i As Integer
    For i = 0 To 99
        Console.Write("")
    Next i
    t2.Start()
 
    '2つのスレッドが終了するまで待つ
    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
//using System.Threading;
//が宣言されているものとする
 
//エントリポイント
public static void Main()
{
    //2つのThreadオブジェクトを作成する
    Thread t1 = new Thread(new ThreadStart(MyThread));
    t1.Name = "1";
    Thread t2 = new Thread(new ThreadStart(MyThread));
    t2.Name = "2";
    //2つ目のスレッドの優先順位を最高にする
    t2.Priority = ThreadPriority.Highest;
 
    //1つのスレッドを開始
    t1.Start();
 
    //しばらく待機の後2つ目のスレッドを開始
    for (int i = 0; i < 100; i++)
        Console.Write("");
    t2.Start();
 
    //2つのスレッドが終了するまで待つ
    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"に実行が移ります。

コメント



ページ情報
  • カテゴリ : .NET
  • 作成日 : 2003-10-21 (火) 06:00:00
  • 作成者 : DOBON!
  • 最終編集日 : 2010-03-21 (日) 01:00:39
  • 最終編集者 : DOBON!
[ トップ ]   [ 編集 | 凍結 | 差分 | バックアップ | 添付 | 複製 | 名前変更 | リロード ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]