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

ご挨拶

明けましておめでとうございます。本年もよろしくお願いいたします。

DOBON.NETの携帯サイトができました

私の作成しているサイト「DOBON.NET」の携帯専用サイト「DOBON.NETモバイル」ができました。現在のコンテンツは.NET Tipsだけですが、.NET Tipsを携帯で読むことができます。「DOBON.NETモバイル」のURLは、

http://dobon.net/i/

です。「DOBON.NETモバイル」について詳しくは

http://dobon.net/mobile.html

をご覧ください。

.NET Tips

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

注意

この記事の最新版は「.NETのマルチスレッドプログラミング」で公開しています。

非同期デリゲートの使い方

.NET Frameworkには、デリゲートを使い、メソッドを非同期に(プログラムの実行をブロックせずに)呼び出す簡単な方法が用意されています。この非同期デリゲートを使用すれば、基本的にすべてのメソッドを非同期で呼び出すことができます。つまり、パラメータや戻り値の受け渡しも簡単に行えます。

この非同期呼び出しにはスレッドプールが使われます。よってマルチスレッドプログラミングやスレッドプールを使う際の注意事項は、非同期デリゲートの使用においても留意する必要があります。なおスレッドプールについては前回詳しく説明しましたので、そちらをご覧ください。

以下に非同期デリゲートを使った簡単な例を示します。この例ではMyMethodメソッドを非同期で呼び出しています。

  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
Imports System
Imports System.Threading
 
Class MainClass
    '非同期で呼び出すメソッドと同じシグネチャのデリゲート
    Delegate Function MyMethodAsyncDelegate( _
        ByVal str As String, ByVal num As Integer) As Integer
 
    '非同期で呼び出すメソッド
    Private Shared Function MyMethod( _
            ByVal str As String, ByVal num As Integer) As Integer
        Dim start As Integer = System.Environment.TickCount
 
        '長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str)
        Thread.CurrentThread.Name = str
        Thread.Sleep(num)
 
        '実際にかかった時間を返す
        Return System.Environment.TickCount - start
    End Function
 
    'エントリポイント
    Public Shared Sub Main()
        'デリゲートオブジェクトの作成
        Dim dlgt As New MyMethodAsyncDelegate(AddressOf MyMethod)
        '非同期呼び出しを開始
        Dim ar As IAsyncResult = _
            dlgt.BeginInvoke("ほげほげ", 1000, Nothing, Nothing)
 
        'メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド"
        Console.WriteLine("メインスレッド名:{0}", _
            Thread.CurrentThread.Name)
 
        '非同期処理の終了を待ち、結果を取得
        Dim ret As Integer = dlgt.EndInvoke(ar)
 
        '結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret)
 
        Console.ReadLine()
    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
using System;
using System.Threading;
 
class MainClass
{
    //非同期で呼び出すメソッドと同じシグネチャのデリゲート
    private delegate 
        int MyMethodAsyncDelegate(string str, int num);
 
    //非同期で呼び出すメソッド
    private static int MyMethod(string str, int num)
    {
        int start = System.Environment.TickCount;
 
        //長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str);
        Thread.CurrentThread.Name = str;
        Thread.Sleep(num);
 
        //実際にかかった時間を返す
        return System.Environment.TickCount - start;
    }
 
    //エントリポイント
    public static void Main()
    {
        //デリゲートオブジェクトの作成
        MyMethodAsyncDelegate dlgt = 
            new MyMethodAsyncDelegate(MyMethod);
        //非同期呼び出しを開始
        IAsyncResult ar = 
            dlgt.BeginInvoke("ほげほげ", 1000, null, null);
 
        //メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド";
        Console.WriteLine("メインスレッド名:{0}", 
            Thread.CurrentThread.Name);
 
        //非同期処理の終了を待ち、結果を取得
        int ret = dlgt.EndInvoke(ar);
 
        //結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret);
 
        Console.ReadLine();
    }
}
[出力結果例]
メインスレッド名:メインスレッド
非同期処理の開始:ほげほげ
処理にかかった時間(ミリ秒):1001

上記の例ではMyMethodメソッドを非同期で呼び出すために、まずMyMethodメソッドと同じシグネチャ(パラメータや戻り値の型などの定義)のデリゲート"MyMethodAsyncDelegate"を定義しています。すると、適切なシグネチャを持つBeginInvokeメソッドとEndInvokeメソッドがこのデリゲートに自動的に定義されます。このBeginInvokeメソッドを呼び出すことにより、MyMethodメソッドの非同期呼び出しが開始されます。BeginInvokeメソッドはIAsyncResultオブジェクトを返し、すぐに制御を戻しますので、MyMethodメソッドが終了するまでブロックすることはありません。結果を取得するためには、EndInvokeメソッドを使います。EndInvokeメソッドを呼び出したときに非同期処理が完了していれば制御はすぐに戻りますが、まだ終わっていなければ終了までブロックします。

さて、非同期デリゲートを使用する際に問題となるのは、「非同期に呼び出したメソッドがいつ終わったかどうやって知るか」という点でしょう。ヘルプ「非同期プログラミングの概要」を参考にすると、その方法には次の4つが挙げられます。

  1. EndInvokeメソッドで呼び出しが終了するまでブロックする
  2. 待機ハンドルを使って待機する
  3. IAsyncResult.IsCompletedプロパティがTrueになるまで待つ
  4. 呼び出しが終了したときにコールバックメソッドが実行されるようにする

1.の方法は一番初めのコードで説明しましたので、残りの3つの方法について説明していきます。

2.待機ハンドルを使って待機する

IAsyncResult.AsyncWaitHandleプロパティにより待機ハンドル(WaitHandle)を取得し、WaitHandle.WaitOneメソッドにより非同期処理が終了するまで待機させることができます。なお待機ハンドルに関しては、.NETプログラミング研究の第22号をご覧ください。

次に簡単な例を示します。EndInvokeを呼び出す前にWaitHandle.WaitOneメソッドを呼び出しています。

  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
Imports System
Imports System.Threading
 
Class MainClass
    '非同期で呼び出すメソッドと同じシグネチャのデリゲート
    Delegate Function MyMethodAsyncDelegate( _
        ByVal str As String, ByVal num As Integer) As Integer
 
    '非同期で呼び出すメソッド
    Private Shared Function MyMethod( _
            ByVal str As String, ByVal num As Integer) As Integer
        Dim start As Integer = System.Environment.TickCount
 
        '長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str)
        Thread.CurrentThread.Name = str
        Thread.Sleep(num)
 
        '実際にかかった時間を返す
        Return System.Environment.TickCount - start
    End Function
 
    'エントリポイント
    Public Shared Sub Main()
        'デリゲートオブジェクトの作成
        Dim dlgt As New MyMethodAsyncDelegate(AddressOf MyMethod)
        '非同期呼び出しを開始
        Dim ar As IAsyncResult = _
            dlgt.BeginInvoke("ほげほげ", 1000, Nothing, Nothing)
 
        'メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド"
        Console.WriteLine("メインスレッド名:{0}", _
            Thread.CurrentThread.Name)
 
        '待機ハンドルがシグナル状態になるまで待機する
        '非同期処理が終了した時にシグナル状態になる
        ar.AsyncWaitHandle.WaitOne()
 
        '結果を取得
        Dim ret As Integer = dlgt.EndInvoke(ar)
 
        '結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret)
 
        Console.ReadLine()
    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
using System;
using System.Threading;
 
class MainClass
{
    //非同期で呼び出すメソッドと同じシグネチャのデリゲート
    private delegate 
        int MyMethodAsyncDelegate(string str, int num);
 
    //非同期で呼び出すメソッド
    private static int MyMethod(string str, int num)
    {
        int start = System.Environment.TickCount;
 
        //長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str);
        Thread.CurrentThread.Name = str;
        Thread.Sleep(num);
 
        //実際にかかった時間を返す
        return System.Environment.TickCount - start;
    }
 
    //エントリポイント
    public static void Main()
    {
        //デリゲートオブジェクトの作成
        MyMethodAsyncDelegate dlgt = 
            new MyMethodAsyncDelegate(MyMethod);
        //非同期呼び出しを開始
        IAsyncResult ar = 
            dlgt.BeginInvoke("ほげほげ", 1000, null, null);
 
        //メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド";
        Console.WriteLine("メインスレッド名:{0}", 
            Thread.CurrentThread.Name);
 
        //待機ハンドルがシグナル状態になるまで待機する
        //非同期処理が終了した時にシグナル状態になる
        ar.AsyncWaitHandle.WaitOne();
 
        //結果を取得
        int ret = dlgt.EndInvoke(ar);
 
        //結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret);
 
        Console.ReadLine();
    }
}

3.IAsyncResult.IsCompletedプロパティがTrueになるまで待つ

非同期処理が終了するとIAsyncResult.IsCompletedプロパティがTrueになりますので、この値を確認することにより、非同期に呼び出したメソッドの終了を知ることができます。この方法は呼び出し元のスレッドを待機させませんので、非同期処理中にユーザーからの入力を受け付けたい時などに使えます。

下の例ではIAsyncResult.IsCompletedプロパティがTrueになるまでループしてから、EndInvokeを呼び出しています。

  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
Imports System
Imports System.Threading
 
Class MainClass
    '非同期で呼び出すメソッドと同じシグネチャのデリゲート
    Delegate Function MyMethodAsyncDelegate( _
        ByVal str As String, ByVal num As Integer) As Integer
 
    '非同期で呼び出すメソッド
    Private Shared Function MyMethod( _
            ByVal str As String, ByVal num As Integer) As Integer
        Dim start As Integer = System.Environment.TickCount
 
        '長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str)
        Thread.CurrentThread.Name = str
        Thread.Sleep(num)
 
        '実際にかかった時間を返す
        Return System.Environment.TickCount - start
    End Function
 
    'エントリポイント
    Public Shared Sub Main()
        'デリゲートオブジェクトの作成
        Dim dlgt As New MyMethodAsyncDelegate(AddressOf MyMethod)
        '非同期呼び出しを開始
        Dim ar As IAsyncResult = _
            dlgt.BeginInvoke("ほげほげ", 1000, Nothing, Nothing)
 
        'メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド"
        Console.WriteLine("メインスレッド名:{0}", _
            Thread.CurrentThread.Name)
 
        '非同期処理が終了するまでループ(ポーリング)する
        While Not ar.IsCompleted
            Console.Write(".")
        End While
 
        '結果を取得
        Dim ret As Integer = dlgt.EndInvoke(ar)
 
        '結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret)
 
        Console.ReadLine()
    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
using System;
using System.Threading;
 
class MainClass
{
    //非同期で呼び出すメソッドと同じシグネチャのデリゲート
    private delegate 
        int MyMethodAsyncDelegate(string str, int num);
 
    //非同期で呼び出すメソッド
    private static int MyMethod(string str, int num)
    {
        int start = System.Environment.TickCount;
 
        //長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str);
        Thread.CurrentThread.Name = str;
        Thread.Sleep(num);
 
        //実際にかかった時間を返す
        return System.Environment.TickCount - start;
    }
 
    //エントリポイント
    public static void Main()
    {
        //デリゲートオブジェクトの作成
        MyMethodAsyncDelegate dlgt = 
            new MyMethodAsyncDelegate(MyMethod);
        //非同期呼び出しを開始
        IAsyncResult ar = 
            dlgt.BeginInvoke("ほげほげ", 1000, null, null);
 
        //メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド";
        Console.WriteLine("メインスレッド名:{0}", 
            Thread.CurrentThread.Name);
 
        //非同期処理が終了するまでループ(ポーリング)する
        while (!ar.IsCompleted)
        {
            Console.Write(".");
        }
 
        //結果を取得
        int ret = dlgt.EndInvoke(ar);
 
        //結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret);
 
        Console.ReadLine();
    }
}

4.呼び出しが終了したときにコールバックメソッドが実行されるようにする

非同期処理が終了した時にコールバックメソッドを呼び出すことができます。コールバックメソッドはAsyncCallbackデリゲートと同じシグネチャである必要があります。また、コールバックメソッドはスレッドプールスレッド(非同期処理が行われたスレッド)で実行されることに注意してください。

次の例ではBeginInvokeメソッドを呼び出す際、コールバックメソッドの他に、呼び出しを開始するために使用したデリゲート"dlgt"を渡すことにより、コールバックメソッド内でEndInvokeメソッドを呼び出せるようにしています。

  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
Imports System
Imports System.Threading
 
Class MainClass
    '非同期で呼び出すメソッドと同じシグネチャのデリゲート
    Delegate Function MyMethodAsyncDelegate( _
        ByVal str As String, ByVal num As Integer) As Integer
 
    '非同期で呼び出すメソッド
    Private Shared Function MyMethod( _
            ByVal str As String, ByVal num As Integer) As Integer
        Dim start As Integer = System.Environment.TickCount
 
        '長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str)
        Thread.CurrentThread.Name = str
        Thread.Sleep(num)
 
        '実際にかかった時間を返す
        Return System.Environment.TickCount - start
    End Function
 
    'エントリポイント
    Public Shared Sub Main()
        'デリゲートオブジェクトの作成
        Dim dlgt As New MyMethodAsyncDelegate(AddressOf MyMethod)
        '非同期呼び出しを開始
        'コールバックメソッドを指定する
        'デリゲートオブジェクトをコールバックメソッドから
        '  IAsyncResult.AsyncStateで取得できるようにする
        Dim ar As IAsyncResult = dlgt.BeginInvoke("ほげほげ", 1000, _
            New AsyncCallback(AddressOf CallbackMethod), dlgt)
 
        'メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド"
        Console.WriteLine("メインスレッド名:{0}", Thread.CurrentThread.Name)
 
        Console.ReadLine()
    End Sub
 
    'コールバックメソッド
    Private Shared Sub CallbackMethod(ByVal ar As IAsyncResult)
        'デリゲートオブジェクトの取得
        Dim dlgt As MyMethodAsyncDelegate = _
            CType(ar.AsyncState, MyMethodAsyncDelegate)
 
        'EndInvokeを呼び出し、結果を取得
        Dim ret As Integer = dlgt.EndInvoke(ar)
 
        '結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret)
    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
 58
 59
using System;
using System.Threading;
 
class MainClass
{
    //非同期で呼び出すメソッドと同じシグネチャのデリゲート
    private delegate 
        int MyMethodAsyncDelegate(string str, int num);
 
    //非同期で呼び出すメソッド
    private static int MyMethod(string str, int num)
    {
        int start = System.Environment.TickCount;
 
        //長い処理があるものとする
        Console.WriteLine("非同期処理の開始:{0}", str);
        Thread.CurrentThread.Name = str;
        Thread.Sleep(num);
 
        //実際にかかった時間を返す
        return System.Environment.TickCount - start;
    }
 
    //エントリポイント
    public static void Main()
    {
        //デリゲートオブジェクトの作成
        MyMethodAsyncDelegate dlgt = 
            new MyMethodAsyncDelegate(MyMethod);
        //非同期呼び出しを開始
        //コールバックメソッドを指定する
        //デリゲートオブジェクトをコールバックメソッドから
        //  IAsyncResult.AsyncStateで取得できるようにする
        IAsyncResult ar = 
            dlgt.BeginInvoke("ほげほげ", 1000,
            new AsyncCallback(CallbackMethod), dlgt);
 
        //メインスレッドで処理
        Thread.CurrentThread.Name = "メインスレッド";
        Console.WriteLine("メインスレッド名:{0}", 
            Thread.CurrentThread.Name);
 
        Console.ReadLine();
    }
 
    //コールバックメソッド
    private static void CallbackMethod(IAsyncResult ar)
    {
        //デリゲートオブジェクトの取得
        MyMethodAsyncDelegate dlgt =
            (MyMethodAsyncDelegate) ar.AsyncState;
 
        //EndInvokeを呼び出し、結果を取得
        int ret = dlgt.EndInvoke(ar);
 
        //結果を表示
        Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret);
    }
}

参考:

コメント



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