[C#] await中の非同期処理の例外をきちんと拾う

非同期処理中に発生した例外が拾えない問題を何とかする話。


  • 2016/09/18 作成
  • 2016/09/18 微修正

例外が拾えない問題

try-catchの中にawaitが入っていれば、その非同期処理中に発生した例外は同期側のtry-catchで拾える、という紹介がそこらじゅうでされている。で、以下のようなコードを書いてみる。

try
{
    await Task.Run(() =>
    {
        // 何か例外が発生しそうな処理
        throw new Exception("なんかあかんみたいやで");
    });
}
catch(Exception ex)
{
    Debug.WriteLine(ex.Message);
}

これをデバッグモードで実行してみると「あれ? 例外が拾えないぞ!?」となる。なんでや!

Releaseビルドなら例外を拾える

ググったらどうやらデバッグモードじゃなければ意図通りの動作をするらしい。

でもデバッグモードが使えないのは不便なので別の方法で解決したい。

デバッグモードだと例外をいろんな所で何回も拾う

で、上記リンク先に以下のようなことが。

  1. デバッグモードでは、本来Task実行スレッドで発生した例外があるという事を伝えるため、一旦そのスレッドで例外を投げる
  2. Task実行スレッド側で例外をキャッチしていなかった場合はユーザコードでハンドルされなかったというダイアログが出る
  3. プログラムを継続させると、改めて呼び出しスレッド側で例外が発生する

例外を拾えないわけではなく、catchが例外を拾う手前でも同じ例外を拾っていて、そこで一時停止している、ということらしい。つまり、Task実行スレッド側で例外をキャッチすればコードの実行は一時停止しないということ。

結局、こうすれば何とかなる。

try
{
    // Task.Run の中で例外を出されるとcatchする前に一時停止してしまうので
    Exception ex_tmp = null;
    await Task.Run(() =>
    {
        try
        {
            // 何か例外が発生しそうな処理
            throw new Exception("なんかあかんみたいやで");
        }
        catch(Exception ex_)
        {
            ex_tmp = ex_; // 例外を一旦キャッチしておく
        }
    });
    if (ex_tmp != null) throw ex_tmp; // 改めて例外を投げ直す
}
catch(Exception ex)
{
    Debug.WriteLine(ex.Message);
}