C# 非同期デリゲート処理(BeginInvoke)での例外を捕捉する

.NET Framework では、デリゲート(C言語で云うところの関数ポインタのようなもの)を使って任意のメソッドを非同期に呼び出すことが可能です。これは「非同期デリゲート」と呼ばれ、内部的にはスレッドプールが使用されています。

ファイルコピーなどの時間のかかる処理を簡単に非同期で処理することができます。

また、.NET Framework 3.5 以降からは、ラムダ式などの導入から汎用的なデリゲートがあらかじめ用意されているため、ほとんどの場合、新たにデリゲートを宣言する必要はなくなりました。

さらに、匿名メソッドやラムダ式を使用することで、非同期処理のメソッド自体も別に宣言する必要がなくなりました。

これらを使用すると、簡単に非同期処理を記述することができるようになりましたが、非同期に呼び出した処理の中で例外が発生した場合、それらを捕捉するにはコールバック用のメソッドを用意してEndInvoke()を呼び出す必要があります。

    static void Main(string[] args)
    {
        int val = 10000;
         
        // 引数:int型 戻り値:bool型 のデリゲート
        Func<intbool> func = x =>
        {
            // 処理の内容をラムダ式で記載する
         
            // ここからローカル変数(val)も使用できる
            System.Threading.Thread.Sleep(val / x);
         
            return true;
        };
         
        // 上で定義したfuncを非同期で呼び出し(引数が0なので内部の除算で例外)
        func.BeginInvoke(0, asyncCallback, func);
         
        // メインスレッドも適当に待機
        System.Threading.Thread.Sleep(10000);
    }
         
    /// <summary>
    /// 非同期コールバック
    /// </summary>
    private static void asyncCallback(IAsyncResult ar)
    {
        try
        {
            // EndInvokeで戻り値を取得できるが、例外もここで捕捉する
            Func<intbool> func = (Func<intbool>)ar.AsyncState;
            bool ret = func.EndInvoke(ar);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

なお、EndInvoke()をメインスレッドから呼び出した場合は、非同期処理が終了するまでブロックされます。

ちなみに、もし EndInvoke()を呼び出さなかった場合、デバッガ上で動作している場合は「~はハンドルされませんでした」とエラーダイアログが表示されますが、Releaseビルドしたexeファイルを直接実行した場合、アプリケーションエラーなどは発生せず、そのまま非同期処理が終わってしまうので注意が必要です。

最後に今回のサンプルでは非同期処理をラムダ式で書きましたが、別途メソッドを定義しても、当然問題はありません。

使用環境: Visual Studio 2010 .NET Framework 3.0 以降

C# メニューリスト