Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

.NETマルチスレッドプログラミング 2:非同期デリゲートとスレッドプール

.NET Frameworkにおけるマルチスレッドプログラミングの基本

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/08/10 12:00

本稿は、.NET Frameworkにおけるマルチスレッドプログラミングの解説です。四部作の「パート2」では、スレッドでフォームやコントロールを扱う方法や、「スレッドプール」「非同期デリゲート」などの事柄について学びます。

目次

目次

はじめに

 前回のパート1では、コンソールアプリケーションのサンプルを作成しながら、.NET Frameworkにおけるスレッドの実行や同期の方法などを学びました。本稿では、スレッドでフォームやコントロールを扱う方法や、「スレッドプール」「非同期デリゲート」などの事柄について解説します。

対象読者

 この記事はパート1の続きですので、パート1からお読みください。

必要な環境

 サンプルはVisual Studio .NET 2003で作成し、.NET Framework 1.1で動作確認をしています。

別スレッドからフォーム、コントロールを扱う

 パート1で幾つかのサンプルを紹介してきましたが、すべてコンソールアプリケーションでした。実はこれには深い訳があります。

 Consoleクラスはスレッドセーフであり、マルチスレッド操作に対して安全が保障されています。ところが、Windowsアプリケーションのコントロール(フォームを含む)は違います。Controlクラスでスレッドセーフが保障されているのは、InvokeBeginInvokeEndInvokeCreateGraphicsメソッドおよびInvokeRequiredプロパティのみです。さらにWindowsフォームはシングルスレッドアパートメント(STA)モデルを使用しており、コントロールのメソッド(あるいはプロパティ)はそのコントロールを作成したスレッド(UIスレッド)からしか呼び出すことができません。つまり、スレッドセーフが保障されているメソッドを除き、コントロールのメソッドを別スレッドから直接呼び出してはいけません。

 これを解決するには、スレッドの境界を越えて実行を行う、マーシャリングが必要になります。.NET FrameworkのControlクラスでは、マーシャリングを確実かつ効率よく行う方法として、InvokeBeginInvoke、およびEndInvokeメソッドが用意されています。

Invokeメソッド

 サンプル「form_01」は、Invokeメソッドを使用した簡単な例です。フォームに配置されたテキストボックスTextBox1にメインスレッドとは別のスレッドから文字列を追加しています。以下にコードの一部を抜粋します。

「VB.NET/form_01/Class1.vb」 (VB.NET)
'Invokeメソッドで使用するデリゲート
Delegate Function WriteLineDelegate(ByVal str As String) As Integer

Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click
    'メインスレッドに名前をつける
    If System.Threading.Thread.CurrentThread.Name Is Nothing Then
        System.Threading.Thread.CurrentThread.Name = "メインスレッド"
    End If

    'WriteLinesメソッドを別スレッドで呼び出す
    'スレッドの作成
    Dim t As New System.Threading.Thread( _
        New System.Threading.ThreadStart( _
        AddressOf WriteLines))
    t.Name = "サブスレッド"
    'スレッドを開始する
    t.Start()
End Sub

'別スレッドで実行するメソッド
Private Sub WriteLines()
    '現在のスレッドの名前を取得
    Dim threadName As String = _
        System.Threading.Thread.CurrentThread.Name

    'WriteLineDelegateの作成
    Dim dlg As New WriteLineDelegate(AddressOf WriteLine)

    'TextBox1に文字列を追加する
    Dim count As Integer = _
        CInt(Me.Invoke(dlg, New Object() {threadName}))
End Sub

'TextBox1に文字列を追加する
Private Function WriteLine(ByVal str As String) As Integer
    '現在のスレッドの名前を取得
    Dim threadName As String = _
        System.Threading.Thread.CurrentThread.Name
    TextBox1.AppendText _
        (("呼び出しもとのスレッドは[" + str + "]" + vbCrLf))
    TextBox1.AppendText _
        (("現在のスレッドは[" + threadName + "]" + vbCrLf))
    Return TextBox1.Lines.Length
End Function
「C#/form_01/Class1.cs」 (C#)
//Invokeメソッドで使用するデリゲート
private delegate int WriteLineDelegate(string str);

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //メインスレッドに名前をつける
    if (System.Threading.Thread.CurrentThread.Name == null)
        System.Threading.Thread.CurrentThread.Name = "メインスレッド";

    //WriteLinesメソッドを別スレッドで呼び出す
    //スレッドの作成
    System.Threading.Thread t =
        new System.Threading.Thread(
        new System.Threading.ThreadStart(WriteLines));
    t.Name = "サブスレッド";
    //スレッドを開始する
    t.Start();
}

//別スレッドで実行するメソッド
private void WriteLines()
{
    //現在のスレッドの名前を取得
    string threadName = System.Threading.Thread.CurrentThread.Name;

    //WriteLineDelegateの作成
    WriteLineDelegate dlg = new WriteLineDelegate(WriteLine);

    //TextBox1に文字列を追加する
    int count = (int) this.Invoke(dlg, new object[] {threadName});
}

//TextBox1に文字列を追加する
private int WriteLine(string str)
{
    //現在のスレッドの名前を取得
    string threadName = System.Threading.Thread.CurrentThread.Name;
    TextBox1.AppendText("呼び出しもとのスレッドは[" + str + "]\r\n");
    TextBox1.AppendText("現在のスレッドは[" + threadName + "]\r\n");
    return TextBox1.Lines.Length;
}

 Thread.CurrentThreadは、現在のスレッドのThreadオブジェクトを取得するプロパティです。また、Thread.Nameは、スレッドの名前を取得、設定するプロパティです。

 このコードでは、ボタンコントロールButton1をクリックすることにより、別スレッドからTextBox1に文字列を追加しています。TextBox1を作成したスレッド(ここではメインスレッド)とは別のスレッドからTextBox1のメソッドを直接呼び出すことはできませんので、TextBox1を操作するWriteLineメソッドを用意し、これをInvokeメソッドで呼び出します。このようにすることにより、WriteLineメソッドはメインスレッドで実行されるようになります。

 Button1をクリックした結果、TextBox1には、

呼び出しもとのスレッドは[サブスレッド]
現在のスレッドは[メインスレッド]

 と表示されます。Invokeメソッドで呼び出されたメソッドはメインスレッドで実行されていることが分かると思います。

 Invokeメソッドを使ってUIスレッド以外のスレッドからコントロールを操作する手順をまとめると、次のようになります。

  1. コントロールのメソッドやプロパティを呼び出すためのメソッドを作成する。
  2. そのメソッドに合ったデリゲートを宣言する。
  3. デリゲートオブジェクトを作成する。
  4. コントロール、あるいはコントロールのあるフォームのInvokeメソッドをデリゲートオブジェクトを指定して呼び出す。

 なお当たり前のことですが、Invokeで呼び出すメソッドはなるべく短時間で終了するようにしてください。あまりに長いと、別スレッドで実行する意味がなくなってしまいます。

BeginInvoke・EndInvokeメソッド

 Invokeメソッドは同期的にデリゲートを実行するため、メソッドが終了するまでブロックします。これに対してBeginInvokeメソッドは非同期的にデリゲートを実行するため、ブロックしません。

 サンプル「form_02」がその例です。

「VB.NET/form_02/Class1.vb」 (VB.NET)
'別スレッドで実行するメソッド
Private Sub WriteLines()
    '現在のスレッドの名前を取得
    Dim threadName As String = _
        System.Threading.Thread.CurrentThread.Name

    'WriteLineDelegateの作成
    Dim dlg As New WriteLineDelegate(AddressOf WriteLine)

    'Invokeを使ってTextBox1に文字列を追加する
    Dim ar As IAsyncResult = _
        Me.BeginInvoke(dlg, New Object() {threadName})

    '戻り値を取得する
    Dim count As Integer = CInt(TextBox1.EndInvoke(ar))
End Sub
「C#/form_02/Class1.cs」 (C#)
//別スレッドで実行するメソッド
private void WriteLines()
{
    //現在のスレッドの名前を取得
    string threadName = System.Threading.Thread.CurrentThread.Name;

    //WriteLineDelegateの作成
    WriteLineDelegate dlg = new WriteLineDelegate(WriteLine);

    //Invokeを使ってTextBox1に文字列を追加する
    IAsyncResult ar = 
        this.BeginInvoke(dlg, new object[] {threadName});

    //戻り値を取得する
    int count = (int) TextBox1.EndInvoke(ar);
}

 EndInvokeメソッドは、BeginInvokeで呼び出したメソッドが終了するまでブロックします(よって上のコードは非同期としている意味がありません)。もし戻り値を取得する必要がなければ、EndInvokeを呼び出す必要はありません。

 InvokeEndInvokeメソッドはスレッドをブロックするため、デッドロックの原因となる危険があります。戻り値を取得する必要がなければ、BeginInvokeメソッドのみを呼び出すのがより安全な方法です。

InvokeRequiredプロパティ

 あるコントロールのメソッドを呼び出すときに、Invokeメソッドを使用する必要があるか調べる方法として、Control.InvokeRequiredプロパティが用意されています。そのコントロールを作成したスレッドがInvokeRequiredを呼び出したスレッドと異なる場合はtrue、それ以外はfalseを返します。

 上記のWriteLinesメソッドを次のように書き換えることにより、riteLinesメソッドをメインスレッドから呼び出したときはWriteLineメソッドを直接呼び出すようにできます(サンプル「form_03」)。

「VB.NET/form_03/Class1.vb」 (VB.NET)
'別スレッドで実行するメソッド
Private Sub WriteLines()
    Dim count As Integer

    '現在のスレッドの名前を取得
    Dim threadName As String = _
        System.Threading.Thread.CurrentThread.Name

    'TextBox1に文字列を追加する
    'Invokeが必要か調べる
    If Me.InvokeRequired Then
        'WriteLineDelegateの作成
        Dim dlg As New WriteLineDelegate(AddressOf WriteLine)
        'InvokeでWriteLineメソッドを呼び出す
        count = CInt(Me.Invoke(dlg, New Object() {threadName}))
    Else
        'WriteLineメソッドを直接呼び出す
        count = Me.WriteLine(threadName)
    End If
End Sub
「C#/form_03/Class1.cs」 (C#)
private void WriteLines()
{
    int count;

    //現在のスレッドの名前を取得
    string threadName = System.Threading.Thread.CurrentThread.Name;

    //TextBox1に文字列を追加する
    //Invokeが必要か調べる
    if (this.InvokeRequired)
    {
        //WriteLineDelegateの作成
        WriteLineDelegate dlg = new WriteLineDelegate(WriteLine);
        //InvokeでWriteLineメソッドを呼び出す
        count = (int) this.Invoke(dlg, new object[] {threadName});
    }
    else
    {
        //WriteLineメソッドを直接呼び出す
        count = this.WriteLine(threadName);
    }
}

 なおInvokeRequiredfalseのときにInvokeでメソッドを呼び出しても特に問題はないようです。

MethodInvoker・EventHandlerデリゲート

 今までの例ではInvokeで実行するデリゲートを独自に定義していましたが、すでに用意されているMethodInvokerまたはEventHandlerデリゲートを使うこともできます。しかも、これらのデリゲートを使用した方がより高速で実行されるということです。

 サンプル「form_04」では、EventHandlerデリゲートを使用しています。

「VB.NET/form_04/Class1.vb」 (VB.NET)
'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click
    'メインスレッドに名前をつける
    If System.Threading.Thread.CurrentThread.Name Is Nothing Then
        System.Threading.Thread.CurrentThread.Name = "メインスレッド"
    End If
    'WriteLinesメソッドを別スレッドで呼び出す
    'スレッドの作成
    Dim t As New System.Threading.Thread( _
        New System.Threading.ThreadStart( _
        AddressOf WriteLines))
    t.Name = "サブスレッド"
    'スレッドを開始する
    t.Start()
End Sub

'別スレッドで実行するメソッド
Private Sub WriteLines()
    'WriteLineDelegateの作成
    Dim dlg As New EventHandler(AddressOf WriteLine)

    'TextBox1に文字列を追加する
    Me.Invoke(dlg, New Object() {Nothing, Nothing})
End Sub

'TextBox1に文字列を追加する
Private Sub WriteLine(ByVal sender As Object, _
    ByVal e As System.EventArgs)
    '現在のスレッドの名前を取得
    Dim threadName As String = _
        System.Threading.Thread.CurrentThread.Name
    TextBox1.AppendText _
        (("現在のスレッドは[" + threadName + "]" + vbCrLf))
End Sub
「C#/form_04/Class1.cs」 (C#)
//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //メインスレッドに名前をつける
    if (System.Threading.Thread.CurrentThread.Name == null)
        System.Threading.Thread.CurrentThread.Name = "メインスレッド";

    //WriteLinesメソッドを別スレッドで呼び出す
    //スレッドの作成
    System.Threading.Thread t =
        new System.Threading.Thread(
        new System.Threading.ThreadStart(WriteLines));
    t.Name = "サブスレッド";
    //スレッドを開始する
    t.Start();
}

//別スレッドで実行するメソッド
private void WriteLines()
{
    //WriteLineDelegateの作成
    EventHandler dlg = new EventHandler(WriteLine);

    //TextBox1に文字列を追加する
    this.Invoke(dlg, new object[] {null, null});
}

//TextBox1に文字列を追加する
private void WriteLine(object sender, System.EventArgs e)
{
    //現在のスレッドの名前を取得
    string threadName = System.Threading.Thread.CurrentThread.Name;
    TextBox1.AppendText("現在のスレッドは[" + threadName + "]\r\n");
}

 MethodInvokerEventHandlerデリゲートを使用したときは、メソッドに引数を渡したり、戻り値を受け取ることができません。EventHandlerには2つの引数がありますが、これらに何を指定してもsenderInvokeを呼び出したコントロール、eEventArgs.Emptyとなります。

待機ハンドル

 「待機ハンドル」と呼ばれるWaitHandleオブジェクトは、スレッドの同期に使われます。このWaitHandleは、後述するThreadPool.RegisterWaitForSingleObjectメソッドや非同期デリゲートで使用されますので、その前に説明しておきます。

 待機ハンドルの状態には「シグナル状態」と「非シグナル状態」の2つがあり、待機ハンドルをどのスレッドも所有していなければ「シグナル状態」、所有していれば「非シグナル状態」です。WaitHandle.WaitOneメソッドなどを使うことにより、待機ハンドルがシグナル状態になるまでスレッドをブロックすることができます。

 .NET FrameworkではWaitHandleクラスから派生したクラスとして、ManualResetEventAutoResetEventおよびMutexクラスが用意されています。その内ここでは、ManualResetEventAutoResetEventについて説明し、Mutexについてはパート3で説明します。ちなみにManualResetEventAutoResetEventは、「同期イベント」または「イベントオブジェクト」と呼ばれています。

ManualResetEvent

 サンプル「waithandle_01」は、ManualResetEventを使った非常に簡単な例です。

「VB.NET/waithandle_01/Class1.vb」 (VB.NET)
Class Class1
    Private Shared manualEvent As System.Threading.ManualResetEvent

    'エントリポイント
    Public Shared Sub Main()
        '非シグナル状態でManualResetEventオブジェクトを作成
        manualEvent = New System.Threading.ManualResetEvent(False)

        'スレッドを作成し、開始する
        Dim t As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething))
        t.Start()

        'Enterキーが押されるまで待機
        Console.WriteLine("Enterキーを押してください")
        Console.ReadLine()

        'シグナル状態にする
        manualEvent.Set()

        Console.WriteLine("メインスレッド終了")

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド
    Private Shared Sub DoSomething()
        Console.WriteLine("別スレッド開始")

        'シグナル状態になるまでスレッドをブロックする
        manualEvent.WaitOne()

        Console.WriteLine("別スレッド終了")
    End Sub
End Class
「C#/waithandle_01/Class1.cs」 (C#)
class Class1
{
    private static System.Threading.ManualResetEvent manualEvent;

    //エントリポイント
    public static void Main()
    {
        //非シグナル状態でManualResetEventオブジェクトを作成
        manualEvent = new System.Threading.ManualResetEvent(false);

        //スレッドを作成し、開始する
        System.Threading.Thread t =
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething));
        t.Start();

        //Enterキーが押されるまで待機
        Console.WriteLine("Enterキーを押してください");
        Console.ReadLine();

        //シグナル状態にする
        manualEvent.Set();

        Console.WriteLine("メインスレッド終了");

        Console.ReadLine();
    }

    //別スレッドで実行するメソッド
    private static void DoSomething()
    {
        Console.WriteLine("別スレッド開始");

        //シグナル状態になるまでスレッドをブロックする
        manualEvent.WaitOne();

        Console.WriteLine("別スレッド終了");
    }
}

 このアプリケーションでは、DoSomethingメソッドを別スレッドで実行しますが、[Enter]キーを押すまでこのスレッドは終了しません。

 このコードでは、まず非シグナル状態でManualResetEventオブジェクト「manualEvent」を作成します。メインスレッドではDoSomethingメソッドを実行する別スレッドを作成した後、[Enter]キーが押されるまで待機します。別スレッドではWaitHandle.WaitOneメソッドを呼び出し、manualEventがシグナル状態になるまで待機します。メインスレッドで[Enter]キーが押されるとWaitHandle.Setメソッドが呼び出され、manualEventがシグナル状態になります。すると、別スレッドのWaitOneメソッドによる待機が解除され、DoSomethingメソッドが終了します。

AutoResetEvent

 次に、ManualResetEventにより、2つのスレッドを交互に実行する例を示します(サンプル「waithandle_02」)。

「VB.NET/waithandle_02/Class1.vb」 (VB.NET)
Class Class1
    Private Shared manualEvent1 As System.Threading.ManualResetEvent
    Private Shared manualEvent2 As System.Threading.ManualResetEvent

    'エントリポイント
    Public Shared Sub Main()
        '非シグナル状態でManualResetEventオブジェクトを作成
        manualEvent1 = New System.Threading.ManualResetEvent(False)
        manualEvent2 = New System.Threading.ManualResetEvent(False)

        'スレッドを2つ作成し、開始する
        Dim t1 As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething1))
        Dim t2 As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething2))
        t1.Start()
        t2.Start()

        'manualEvent1をシグナル状態にする
        manualEvent1.Set()

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド1
    Private Shared Sub DoSomething1()
        Dim i As Integer
        For i = 0 To 9
            'manualEvent1がシグナル状態になるまでスレッドをブロック
            manualEvent1.WaitOne()
            'manualEvent1を非シグナル状態にする
            manualEvent1.Reset()

            Console.WriteLine("1")

            'manualEvent2をシグナル状態にする
            manualEvent2.Set()
        Next i
    End Sub


    '別スレッドで実行するメソッド2
    Private Shared Sub DoSomething2()
        Dim i As Integer
        For i = 0 To 9
            'manualEvent2がシグナル状態になるまでスレッドをブロック
            manualEvent2.WaitOne()
            'manualEvent2を非シグナル状態にする
            manualEvent2.Reset()

            Console.WriteLine("2")

            'manualEvent1をシグナル状態にする
            manualEvent1.Set()
        Next i
    End Sub 'DoSomething2
End Class 'Class1
「C#/waithandle_02/Class1.cs」 (C#)
class Class1
{
    private static System.Threading.ManualResetEvent manualEvent1;
    private static System.Threading.ManualResetEvent manualEvent2;

    //エントリポイント
    public static void Main()
    {
        //非シグナル状態でManualResetEventオブジェクトを作成
        manualEvent1 = new System.Threading.ManualResetEvent(false);
        manualEvent2 = new System.Threading.ManualResetEvent(false);

        //スレッドを2つ作成し、開始する
        System.Threading.Thread t1 =
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething1));
        System.Threading.Thread t2 =
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething2));
        t1.Start();
        t2.Start();

        //manualEvent1をシグナル状態にする
        manualEvent1.Set();

        Console.ReadLine();
    }

    //別スレッドで実行するメソッド1
    private static void DoSomething1()
    {
        for (int i = 0; i < 10; i++)
        {
            //manualEvent1がシグナル状態になるまでスレッドをブロック
            manualEvent1.WaitOne();
            //manualEvent1を非シグナル状態にする
            manualEvent1.Reset();

            Console.WriteLine("1");

            //manualEvent2をシグナル状態にする
            manualEvent2.Set();
        }
    }

    //別スレッドで実行するメソッド2
    private static void DoSomething2()
    {
        for (int i = 0; i < 10; i++)
        {
            //manualEvent2がシグナル状態になるまでスレッドをブロック
            manualEvent2.WaitOne();
            //manualEvent2を非シグナル状態にする
            manualEvent2.Reset();

            Console.WriteLine("2");

            //manualEvent1をシグナル状態にする
            manualEvent1.Set();
        }
    }
}

 このプログラムを実行すると、「1」と「2」が交互に出力されます。

 上記のコードではManualResetEvent.WaitOneメソッドの直後でResetメソッドを呼び出して非シグナル状態に戻していますが、ManualResetEventの代わりにAutoResetEventを使うことにより、この手間が省けます。AutoResetEventではシグナルを待機中のスレッドがすべて解放されると、自動的に非シグナル状態にリセットされるのです(待機中のスレッドがない場合は、無限にシグナル状態のままとなります)。

 上記のコードをAutoResetEventを使って書き換えると、次のようになります(サンプル「waithandle_03」)。

「VB.NET/waithandle_03/Class1.vb」 (VB.NET)
Class Class1
    Private Shared autoEvent1 As System.Threading.AutoResetEvent
    Private Shared autoEvent2 As System.Threading.AutoResetEvent

    'エントリポイント
    Public Shared Sub Main()
        '非シグナル状態でManualResetEventオブジェクトを作成
        autoEvent1 = New System.Threading.AutoResetEvent(False)
        autoEvent2 = New System.Threading.AutoResetEvent(False)

        'スレッドを2つ作成し、開始する
        Dim t1 As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething1))
        Dim t2 As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething2))
        t1.Start()
        t2.Start()

        'manualEvent1をシグナル状態にする
        autoEvent1.Set()

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド1
    Private Shared Sub DoSomething1()
        Dim i As Integer
        For i = 0 To 9
            'manualEvent1がシグナル状態になるまでスレッドをブロック
            autoEvent1.WaitOne()

            Console.WriteLine("1")

            'manualEvent2をシグナル状態にする
            autoEvent2.Set()
        Next i
    End Sub

    '別スレッドで実行するメソッド2
    Private Shared Sub DoSomething2()
        Dim i As Integer
        For i = 0 To 9
            'manualEvent2がシグナル状態になるまでスレッドをブロック
            autoEvent2.WaitOne()

            Console.WriteLine("2")

            'manualEvent1をシグナル状態にする
            autoEvent1.Set()
        Next i
    End Sub
End Class
「C#/waithandle_03/Class1.cs」 (C#)
class Class1
{
    private static System.Threading.AutoResetEvent autoEvent1;
    private static System.Threading.AutoResetEvent autoEvent2;

    //エントリポイント
    public static void Main()
    {
        //非シグナル状態でManualResetEventオブジェクトを作成
        autoEvent1 = new System.Threading.AutoResetEvent(false);
        autoEvent2 = new System.Threading.AutoResetEvent(false);

        //スレッドを2つ作成し、開始する
        System.Threading.Thread t1 =
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething1));
        System.Threading.Thread t2 =
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething2));
        t1.Start();
        t2.Start();

        //manualEvent1をシグナル状態にする
        autoEvent1.Set();

        Console.ReadLine();
    }

    //別スレッドで実行するメソッド1
    private static void DoSomething1()
    {
        for (int i = 0; i < 10; i++)
        {
            //manualEvent1がシグナル状態になるまでスレッドをブロック
            autoEvent1.WaitOne();

            Console.WriteLine("1");

            //manualEvent2をシグナル状態にする
            autoEvent2.Set();
        }
    }

    //別スレッドで実行するメソッド2
    private static void DoSomething2()
    {
        for (int i = 0; i < 10; i++)
        {
            //manualEvent2がシグナル状態になるまでスレッドをブロック
            autoEvent2.WaitOne();

            Console.WriteLine("2");

            //manualEvent1をシグナル状態にする
            autoEvent1.Set();
        }
    }
}

 ManualResetEventAutoResetEventおよびMutexクラスはWin32同期ハンドルをカプセル化したものですので、Monitorクラスと比較すると移植性に劣りますし、パフォーマンスも悪いようです。できることなら、Monitorクラスを使うべきです。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • どぼん!(ドボン!)

    DOBON.NET内で.NET Frameworkの機能を紹介したWebサイト.NET Tipsやメールマガジン「.NETプログラミング研究」の発行人。

バックナンバー

連載:.NET Frameworkにおけるマルチスレッドプログラミングの基本
All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5