高度編:C# 非同期プログラミングのベストプラクティスと注意点
1. デッドロックを避けるための設計
非同期プログラミングでよくある問題の一つがデッドロックです。デッドロックとは、二つ以上のタスクが互いにリソースの解放を待っているためにどちらも進行できなくなる状態です。デッドロックを防ぐためには、ConfigureAwait(false)
を使用し、コンテキストの捕捉を防止することが推奨されます。
async Task MyTaskAsync()
{
await Task.Delay(1000).ConfigureAwait(false); // デッドロック防止
Console.WriteLine("タスク完了");
}
これにより、UIスレッドのコンテキストを捕捉することなく、バックグラウンドで安全に非同期処理が実行されます。
2. 非同期処理におけるキャンセル機構 (CancellationToken)
非同期タスクをキャンセルするためには、CancellationToken
を使用します。これにより、非同期処理が不要になった場合に安全にタスクを中断できます。
async Task DoWorkAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested(); // キャンセルをチェック
await Task.Delay(1000);
Console.WriteLine("作業中...");
}
}
var cts = new CancellationTokenSource();
var task = DoWorkAsync(cts.Token);
// 3秒後にキャンセル
await Task.Delay(3000);
cts.Cancel();
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("タスクがキャンセルされました");
}
上記のコードでは、CancellationToken
を使って非同期タスクをキャンセルしています。ThrowIfCancellationRequested
を使うことで、タスクの中で定期的にキャンセルが要求されているか確認できます。
3. 非同期処理の例外処理におけるベストプラクティス
非同期処理で発生する例外を適切にハンドリングするためには、try-catch
ブロックを用いて例外をキャッチします。また、Task.WhenAll
などで複数のタスクが実行される場合、AggregateException
がスローされる可能性があるため、例外を集約して処理することが必要です。
try
{
await Task.WhenAll(Task1(), Task2());
}
catch (AggregateException ex)
{
foreach (var innerException in ex.InnerExceptions)
{
Console.WriteLine($"例外: {innerException.Message}");
}
}
4. 非同期メソッドの単体テストの書き方
非同期メソッドのテストは、async
をサポートする単体テストフレームワーク(例えば、NUnit や xUnit)を使用することで簡単に行うことができます。async Task
でテストメソッドを定義し、非同期タスクをテストします。
// NUnit を使用した非同期メソッドのテスト
[Test]
public async Task MyAsyncMethod_ShouldReturnCorrectValue()
{
int result = await MyAsyncMethod();
Assert.AreEqual(42, result);
}
async Task
を使った単体テストは、非同期処理の動作を確認するために重要です。エラーのハンドリングやキャンセル処理の検証も行うことで、非同期メソッドの安定性を向上させましょう。