AsyncResult

これまでは、BeginInvokeで非同期処理を実行してから、EndInvokeで非同期処理が終わるのを待っていた・・・。
しかし、これだと同期的に処理している事と変わらない・・・。
BeginInvokeで非同期処理を実行した後も、Mainは先の処理を進めつつ、一方で非同期処理完了後のコールバックで終了を通知する形が本来の非同期処理と言えるだろう・・・。

コールバックメソッドの引数IAsyncResultをAsyncResultにキャストすると、IAsyncResultではアクセスできないプロパティを取得できる・・・。

AsyncResult クラス (System.Runtime.Remoting.Messaging)

その中には非同期処理に使ったデリゲートもあり、EndInvokeで結果の値を取得出来たりする・・・。

Main側でEndInvokeを呼ばず、コールバック側でEndInvokeを呼んで結果を取得する・・・。

int型a, bを引数に取り、戻り値はintになるメソッドのデリゲートAsyncronousFuncDelegate・・・。

asyncronousFunc、callback、を生成して、asyncronousFunc.BeginInvokeには、10、20とcallbackなど指定・・・。
"AsyncronousFunc..."と出力したら終わりなのだが、コンソールだとMainが終わってしまうと非同期処理側も実行されないので、Console.ReadKeyで入力されるまで待つ・・・。
(結局、実質的には同期的になっている・・・。Windowsアプリケーションなら終わらないで待てるが・・・。)

非同期処理AsyncronousFuncは、3秒休止したら引数のaとbの足した結果を返す・・・。

非同期処理完了後のコールバックでは、引数のarをIAsyncResultからAsyncResultにキャストしてasyncResに・・・。
asyncRes.AsyncDelegateで非同期処理に使ったasyncronousFuncを取得できる・・・。(objectで格納されてるのでAsyncronousFuncDelegateにキャストする必要あり・・・。)
これでasyncronousFunc.EndInvokeでAsyncronousFuncの戻り値resultを取得できる・・・。

AsyncronousFunc...
result = 30
続行するには何かキーを押してください . . .

コンソールだからMain側は結局待ったが、Windowsアプリケーションなら、Main側が終了してから随分あとに通知することもできる・・・。

Sample/dotnet/AsyncResult/AsyncResult/src/AsyncResult_ at master · bg1bgst333/Sample · GitHub

AsyncCallback

AsyncCallbackデリゲートに、非同期処理完了後に呼び出すメソッドを指定できる・・・。

AsyncCallback デリゲート (System)

まず、デリゲートの生成から・・・。

非同期処理で実行するasyncronousFunc、非同期処理完了後に呼び出すcallbackを生成・・・。

AsyncronousFuncに引数がなければ、asyncronousFunc.BeginInvokeの最初の引数にcallbackを指定する・・・。
(引数がある場合は、引数の分の後に指定・・・。)

AsyncronousFuncは"AsyncronousFunc(i)"の出力、AsyncCallbackFuncは"AsyncCallbackFunc!"の出力をする・・・。

asyncronousFunc.EndInvokeして、すぐ"Finish!"を出力して終わると、AsyncCallbackFuncを実行する暇もなく終わってしまう場合がある・・・。
なので、Thread.Sleepで3秒休止すると、AsyncCallbackFuncが実行されてから、Mainのその後の処理という流れになる・・・。

AsyncronousFunc(1)
AsyncronousFunc(2)
AsyncronousFunc(3)
AsyncronousFunc(4)
AsyncronousFunc(5)
AsyncCallbackFunc!
Finish!
続行するには何かキーを押してください . . .

こんな風に非同期処理AsyncronousFuncの後、AsyncCallbackFuncが実行されて終わる・・・。

Sample/dotnet/AsyncCallback/AsyncCallback/src/AsyncCallback_ at master · bg1bgst333/Sample · GitHub

IAsyncResult

IAsyncResultを使って、実行している非同期処理の状態を取得できる・・・。

IAsyncResult インターフェイス (System)

今回は、IAsyncResult.IsCompletedで非同期処理が完了しているかを1秒毎に確認する・・・。

今回は、引数も戻り値もないAsyncronousFuncDelegateに戻す・・・。
BeginInvokeで非同期処理を実行し、IAsyncResultインターフェイスとしてiarを取得・・・。

AsyncronousFuncは、

"AsyncronousFunc(i)"の出力に戻す・・・。

Main側では、

非同期処理の完了を表すiar.IsCompletedがfalseの間は、"AsyncronousFunc Running..."と出力して1秒休止を繰り返す・・・。
非同期処理が完了してtrueになればここを抜ける・・・。

すでに非同期処理は完了しているので、asyncronousFunc.EndInvokeはすぐ終わる・・・。

最後に"Finish!"と出力・・・。

AsyncronousFunc Running...
AsyncronousFunc(1)
AsyncronousFunc Running...
AsyncronousFunc(2)
AsyncronousFunc Running...
AsyncronousFunc(3)
AsyncronousFunc Running...
AsyncronousFunc(4)
AsyncronousFunc Running...
AsyncronousFunc(5)
AsyncronousFunc Running...
Finish!
続行するには何かキーを押してください . . .

このように、"AsyncronousFunc(5)"までいったら完了なので、"Finish!"で終わっている・・・。
("AsyncronousFunc(5)"のあと"AsyncronousFunc Running..."を出すだけの隙間時間はあるが・・・。)

Sample/dotnet/IAsyncResult/IAsyncResult/src/IAsyncResult_ at master · bg1bgst333/Sample · GitHub

EndInvoke

EndInvokeでは、非同期処理メソッドの戻り値を受け取ることができるし、参照渡しで受け取ることもできる・・・。

今回は、2つの値の足し算を非同期処理でさせて、結果を取得する・・・。

今回のAsyncronousFuncDelegateは、引数にint型のa, bとint型の参照渡しでrefValue、戻り値がintという感じに・・・。

渡す非同期処理メソッドAsyncronousFuncは、

aとbを足してresultに格納・・・。
5秒休止したあと、refValueにも結果を格納・・・。
戻り値としてresultを返す・・・。

Main側は、

BeginInvokeの最初の3つは、AsyncronousFuncに渡す引数・・・。
可変引数なので、後ろの2つ以外は引数の数だけ増える・・・。

"Calculating..."と出力・・・。

EndInvokeの最初も可変引数で、参照渡しの引数の数だけ増える・・・。
でrefValueを指定し、戻り値resultも返ってくる・・・。

resultとrefValueを出力・・・。

Calculating...
result = 30
refValue = 30
続行するには何かキーを押してください . . .

戻り値にも参照渡しにも計算結果が格納されている・・・。

Sample/cs/EndInvoke/EndInvoke/src/EndInvoke_ at master · bg1bgst333/Sample · GitHub

BeginInvoke

ThreadやThreadPoolを使わなくても、デリゲートで非同期処理が実行できる・・・。
(実際には内部でThreadPoolを使っているのだが・・・。)

Calling Synchronous Methods Asynchronously

delegate型を定義すると、C#コンパイラが自動でBeginInvoke、EndInvokeなどのメソッドを用意してくれて、デリゲートオブジェクトはそれらを使うことができる・・・。
(Delegateなどの基底クラスメソッドとかではないようなので、.NET FrameworkカテゴリではなくC#カテゴリにした・・・。)

引数も戻り値も無いデリゲートAsyncronousFuncDelegateを定義・・・。

AsyncronousFuncを非同期で実行するAsyncronousFuncDelegateオブジェクトasyncronousFuncを生成・・・。
AsyncronousFuncは、

AsyncronousFunc(i)の表示を1秒毎に計5回繰り返す・・・。

asyncronousFunc.BeginInvokeで非同期実行開始・・・。

こちらはMain(i)の表示を1秒毎に計3回繰り返す・・・。
なので、Mainの方が先に終わる・・・。

asyncronousFunc.EndInvokeで非同期処理が終わるまで待っている状態になる・・・。

asyncronousFunc.EndInvokeから抜けたら"Finish!"と表示・・・。

Main(1)
AsyncronousFunc(1)
Main(2)
AsyncronousFunc(2)
Main(3)
AsyncronousFunc(3)
AsyncronousFunc(4)
AsyncronousFunc(5)
Finish!
続行するには何かキーを押してください . . .

(3)までは、MainとAsyncronousFuncは1つずつ同時並行で出力される・・・。
しかし、AsyncronousFuncは(4)と(5)が残っているので、それらが出力し終わるまで、Mainは待ち続ける・・・。
そして終わったら、"Finish!"を表示・・・。

Sample/cs/BeginInvoke/BeginInvoke/src/BeginInvoke_ at master · bg1bgst333/Sample · GitHub

ThreadPool.QueueUserWorkItem

ThreadPool.QueueUserWorkItemの2番目の引数には、実行するThreadFuncに渡すためのパラメータを指定できる・・・。

ThreadPool.QueueUserWorkItem メソッド (WaitCallback, Object) (System.Threading)

ワークアイテムに番号を振る・・・。

こんな感じで、1~3をパラメータとして渡す・・・。

指定したパラメータはobject型stateとして渡されるので、intに戻してこれも"["と"]"で括って出力してあげる・・・。

Main(1)
ThreadFunc[1](1)
Main(2)
ThreadFunc[1](2)
ThreadFunc[2](1)
Main(3)
ThreadFunc[1](3)
ThreadFunc[2](2)
Main(4)
ThreadFunc[1](4)
ThreadFunc[2](3)
Main(5)
ThreadFunc[1](5)
ThreadFunc[3](1)
ThreadFunc[2](4)
ThreadFunc[3](2)
ThreadFunc[2](5)
ThreadFunc[3](3)
ThreadFunc[3](4)
ThreadFunc[3](5)
続行するには何かキーを押してください . . .

これでどのワークアイテムかわかりやすくなった・・・。

Sample/dotnet/ThreadPool/QueueUserWorkItem/src/ThreadPool_ at master · bg1bgst333/Sample · GitHub

ThreadPool

定量のスレッドを準備しておき、キューに登録したワークアイテム(実行したい処理(タスク))にそれぞれ割り当てて、終わった後も再び使いまわせるような仕組みをスレッドプールという・・・。

ThreadPoolは、そのスレッドプールを扱うクラス・・・。

ThreadPool クラス (System.Threading)

Mainでのループ処理に加えて、スレッドプールにThreadFuncのタスクを3つ割り当てる・・・。

まず、WaitCallbackデリゲート(これはSystem.Threading名前空間に用意されている既定もの)のwaitCallbackにThreadFuncを登録・・・。

ThreadFuncは、

前回までと似ているが、引数にobject型stateがある部分だけ違う・・・。
これは、WaitCallbackデリゲートの引数型がそうなっているから・・・。

WaitCallback デリゲート (System.Threading)

今回はstateは使わない・・・。

ThreadPool.QueueUserWorkItemでwaitCallbackを3回登録・・・。
これでスレッドプールで3つ並行してループ処理が走ることになる・・・。

さらに、メイン側もループを用意・・・。
なのでメインスレッドとスレッドプールを合わせると4つ並行・・・。

メインスレッドが先に終わってしまうと、スレッドプールの残りの出力が出ない場合があるので、何かキーが押されるまで待つ・・・。

実行すると、

Main(1)
ThreadFunc(1)
ThreadFunc(2)
Main(2)
ThreadFunc(1)
ThreadFunc(1)
ThreadFunc(3)
Main(3)
ThreadFunc(2)
ThreadFunc(2)
ThreadFunc(4)
Main(4)
ThreadFunc(3)
ThreadFunc(3)
ThreadFunc(5)
Main(5)
ThreadFunc(4)
ThreadFunc(4)
ThreadFunc(5)
ThreadFunc(5)
続行するには何かキーを押してください . . .

結果だけ見るとこうだが、実際に実行してみると、1秒ごとのタイミングじゃないタイミングで出力されたりする・・・。
そして、一気に進むやつと遅れるやつが出てくる・・・。

Sample/dotnet/ThreadPool/ThreadPool/src/ThreadPool_ at master · bg1bgst333/Sample · GitHub