ここまで、非同期メソッドの基本的な使い方について見てきました。概要の最後として、例外処理の書き方を見ておきましょう。
通常と同じ記述の例外処理
実は下に示すように、例外処理についても同期コードとほぼほぼ同じ書き方で対応することができます。これまでの非同期処理システムであるAPMやEAPなどではただ非同期処理をするだけでも手間な感じでしたが、例外処理が含まるとなおさら面倒なことになっていました。こういった煩雑さがなく可読性に優れた記述ができるので、安心して利用することができると思います。
private void Button_Click(object sender, RoutedEventArgs e) { try { this.button.IsEnabled = false; throw new Exception(); } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { this.button.IsEnabled = true; } }
private async void Button_Click(object sender, RoutedEventArgs e) { try { this.button.IsEnabled = false; await Task.Run(() => { throw new Exception(); }); } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { this.button.IsEnabled = true; } }
通知される例外は最初のひとつだけ
以前TPL入門 (12) - タスクの例外処理でも紹介しましたが、Task中で発生し処理されなかった例外はTask自身によって捕捉され、AggregateException型にラップされてスローされます。というのも、以下の8行目 (=Task.WhenAll) のように複数のTaskをひとつのTaskにまとめた場合など、複数の例外が同時に発生することが考慮されているためです。
private static void DoSomething() { try { var task1 = Task.Run(() => { throw new InvalidCastException(); }); var task2 = Task.Run(() => { throw new ArgumentException(); }); var task3 = Task.Run(() => { throw new NotSupportedException(); }); var task = Task.WhenAll(task1, task2, task3); task.Wait(); } catch (AggregateException ex) { Console.WriteLine(ex.GetType()); foreach (var inner in ex.InnerExceptions) Console.WriteLine("\\t{0}", inner.GetType()); } } /* System.AggregateException System.InvalidCastException System.ArgumentException System.NotSupportedException */
しかし、以下のようにTaskをawaitすると結果が異なります。
private static async void DoSomething() { try { var task1 = Task.Run(() => { throw new InvalidCastException(); }); var task2 = Task.Run(() => { throw new ArgumentException(); }); var task3 = Task.Run(() => { throw new NotSupportedException(); }); await Task.WhenAll(task1, task2, task3); } catch (Exception ex) { Console.WriteLine(ex.GetType()); } } /* System.InvalidCastException */
このように、複数例外が存在する場合でもawaitから発行される例外は最初のひとつだけとなるので注意が必要です。