.NETプログラミング研究 第24号 †
ご挨拶 †
明けましておめでとうございます。本年もよろしくお願いいたします。
DOBON.NETの携帯サイトができました †
私の作成しているサイト「DOBON.NET」の携帯専用サイト「DOBON.NETモバイル」ができました。現在のコンテンツは.NET Tipsだけですが、.NET Tipsを携帯で読むことができます。「DOBON.NETモバイル」のURLは、
https://dobon.net/i/
です。「DOBON.NETモバイル」について詳しくは
https://dobon.net/mobile.html
をご覧ください。
.NET Tips †
.NETのマルチスレッドプログラミング その6 †
非同期デリゲートの使い方 †
.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つが挙げられます。
- EndInvokeメソッドで呼び出しが終了するまでブロックする
- 待機ハンドルを使って待機する
- IAsyncResult.IsCompletedプロパティがTrueになるまで待つ
- 呼び出しが終了したときにコールバックメソッドが実行されるようにする
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)
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)
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 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;
int ret = dlgt.EndInvoke(ar);
Console.WriteLine("処理にかかった時間(ミリ秒):{0}", ret);
}
}
|
参考:
コメント †