2009年9月26日土曜日

非同期デリゲート

.net では Thread、タイマーなどマルチスレッドを実現する方法が
いくつか用意されています。
今回のデリゲートもそのうちのひとつで、
スレッドの優先順位など細かい制御はできないものの、
任意の型のパラメーターをいくつでも渡せたり、
戻り値が簡単に取れるのはなにかと使い勝手がよいと思います。

実際の使用例は以下の通りです。

// 何かするデリゲート
delegate string SomeDelegate();
private void button1_Click(object sender, EventArgs e)
{
    // デリゲートの実装
    SomeDelegate some = new SomeDelegate(delegate
    {
        // 5 秒後 "hoge" を返します。
        System.Threading.Thread.Sleep(5000);
        return "hoge";
    });

    // SomeDelegate を非同期に呼び出します。
    IAsyncResult ar = some.BeginInvoke(null, null);

    // SomeDelegate から戻り値を得ます。
    Console.WriteLine("returned : " + some.EndInvoke(ar));
}


ボタンを押して 5 秒後、出力ウィンドウに

returned : hoge

と出力されます。

ただし、上の例では EndInvoke() をメインスレッドで
呼び出すことになるので、デリゲートが実行されている間
せっかく非同期に呼び出したのにメインスレッドが停止してしまいます。
(今回の場合、フォームが固まります。)
できればこれは避けたいので、少し実装に手間が増えますが
コールバック関数を用意します。

{
    /* BeginInvoke() の直前まで同じなので省略 */

    // AsyncCallback デリゲート
    AsyncCallback callback = new AsyncCallback(some_Completed);

    // 第一引数にコールバックメソッドを指定します。
    IAsyncResult ar = some.BeginInvoke(callback, null);
}

// SomeDelgate の終了時にコールされます。
private void some_Completed(IAsyncResult async)
{
    // AsyncResult にキャストします。
    AsyncResult asyncResult = (AsyncResult)async;

    // AsyncDelegate で SomeDelegate のインスタンスが取得できます。
    SomeDelegate some = (SomeDelegate)asyncResult.AsyncDelegate;
    Console.WriteLine("returned : " + some.EndInvoke(async));
}


メインスレッドでデリゲートの完了を待たないので
処理自体は先ほどと同じですが、フォームが固まることはありません。

最後になりましたが、デリゲート自体は ThreadPool を利用した技術なので
同時実行数には制限があります。
場合によってはデッドロックが発生することもあるそうなのですが、
特に必要に迫られたことがないのでこの辺あんまり調べていないです。
まぁそんな依存のある非同期処理ならコールバックで
次の非同期処理を BeginInvoke すればよいかと思います。

0 件のコメント:

コメントを投稿