┏第24号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜メニュー ■ご挨拶 ■.NET Tips ・.NETのマルチスレッドプログラミング その6 - 非同期デリゲートの使い方 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜メニュー ─────────────────────────────── ■ご挨拶 ─────────────────────────────── 明けましておめでとうございます。本年もよろしくお願いいたします。 ●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 Frameworkには、デリゲートを使い、メソッドを非同期に(プロ グラムの実行をブロックせずに)呼び出す簡単な方法が用意されてい ます。この非同期デリゲートを使用すれば、基本的にすべてのメソッ ドを非同期で呼び出すことができます。つまり、パラメータや戻り値 の受け渡しも簡単に行えます。 この非同期呼び出しにはスレッドプールが使われます。よってマルチ スレッドプログラミングやスレッドプールを使う際の注意事項は、非 同期デリゲートの使用においても留意する必要があります。なおスレ ッドプールについては前回詳しく説明しましたので、そちらをご覧く ださい。 以下に非同期デリゲートを使った簡単な例を示します。この例では MyMethodメソッドを非同期で呼び出しています。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 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つの方法に ついて説明していきます。 ・非同期プログラミングの概要 http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpovrasynchronousprogrammingoverview.asp 2.待機ハンドルを使って待機する IAsyncResult.AsyncWaitHandleプロパティにより待機ハンドル( WaitHandle)を取得し、WaitHandle.WaitOneメソッドにより非同期処 理が終了するまで待機させることができます。なお待機ハンドルに関 しては、.NETプログラミング研究の第22号をご覧ください。 次に簡単な例を示します。EndInvokeを呼び出す前に WaitHandle.WaitOneメソッドを呼び出しています。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 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を呼び出しています。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 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メソッドを呼び出せ るようにしています。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 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); } } //・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 参考: ・非同期プログラミングの概要 http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpovrasynchronousprogrammingoverview.asp =============================== ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2003-Oct 2004)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 - 2004 DOBON! All rights reserved. ===============================