async / await
| 対応: | C# 5.0(2012) |
|---|
非同期メソッドを定義・呼び出すためのキーワードです。『async』でメソッドを非同期化し、『await』で非同期操作の完了を待機します。
構文
// 非同期メソッドの定義(戻り値なし)
async Task MethodName()
{
await 非同期処理;
}
// 非同期メソッドの定義(戻り値あり)
async Task<T> MethodName()
{
T result = await 非同期処理;
return result;
}
// 非同期メソッドの呼び出し
await MethodName();
T value = await MethodName();
メソッド一覧
| キーワード・型 | 概要 |
|---|---|
| async | メソッドを非同期メソッドとして宣言します。メソッド内で『await』が使えるようになります。 |
| await | 非同期操作の完了を待機します。完了するまでスレッドをブロックせず制御を呼び出し元に返します。 |
| Task | 戻り値なしの非同期操作を表す型です。 |
| Task<T> | 戻り値 T を持つ非同期操作を表す型です。 |
| ValueTask<T> | 同期完了が多い場合に使うヒープ割り当てを抑えた軽量な非同期型です。 |
| async void | イベントハンドラー専用の非同期メソッドです。例外を呼び出し元に伝播できないため注意が必要です。 |
サンプルコード
『PrintGreetingAsync()』は戻り値なしの非同期メソッド(async Task)です。『AddAsync()』は戻り値ありの非同期メソッド(async Task<T>)です。
Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
async Task PrintGreetingAsync(string name)
{
await Task.Delay(100); // 100ms の遅延をシミュレート
Console.WriteLine($"Hello, {name}!");
}
async Task<int> AddAsync(int a, int b)
{
await Task.Delay(50); // 非同期処理をシミュレート
return a + b;
}
// 複数の非同期処理を順番に実行する
async Task RunSequentialAsync()
{
await PrintGreetingAsync("桐生一馬");
await PrintGreetingAsync("真島吾朗");
int result = await AddAsync(3, 4);
Console.WriteLine($"3 + 4 = {result}");
}
// エントリーポイント(.NET 5 以降は Main を async にできる)
await RunSequentialAsync();
Console.WriteLine("完了");
dotnet script async_await.csx Hello, 桐生一馬! Hello, 真島吾朗! 3 + 4 = 7 完了
実践パターン: Task.WhenAll で並列実行
ParallelAsync.cs
using System;
using System.Threading.Tasks;
// 複数の非同期処理を並列で実行する
async Task<string> FetchDataAsync(string name, int delayMs)
{
await Task.Delay(delayMs);
return $"{name} のデータ";
}
// Task.WhenAll: 全タスクが完了するまで並列で待機する
async Task RunParallelAsync()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
// 3つのタスクを同時に開始する
Task<string> task1 = FetchDataAsync("桐生一馬", 1000);
Task<string> task2 = FetchDataAsync("真島吾朗", 1500);
Task<string> task3 = FetchDataAsync("秋山駿", 800);
// 全タスクの完了を並列で待機する(最長の1500msで完了)
string[] results = await Task.WhenAll(task1, task2, task3);
foreach (var result in results)
Console.WriteLine(result);
Console.WriteLine($"経過時間: {sw.ElapsedMilliseconds}ms"); // 約1500ms
}
await RunParallelAsync();
dotnet script ParallelAsync.csx 桐生一馬 のデータ 真島吾朗 のデータ 秋山駿 のデータ 経過時間: 1521ms
順次実行(await を3回)では合計3300msかかりますが、『Task.WhenAll()』で並列実行すると最長のタスクに相当する約1500msで完了します。
よくあるミス1: async void
『async void』はイベントハンドラー以外では使わないでください。例外を呼び出し元に伝播できず、アプリケーションがクラッシュします。
async_void_ng.cs
using System;
using System.Threading.Tasks;
// NG: async void はイベントハンドラー以外で使わない
async void FetchDataBad()
{
await Task.Delay(100);
throw new Exception("エラー!"); // 呼び出し元は例外をキャッチできない
}
dotnet run Unhandled exception. System.Exception: エラー! at Program.<>c__DisplayClass0_0.<<Main>$>b__0() in Program.cs:line 8 Process terminated. Unhandled exception.
// OK: 通常は async Task を返す
async Task FetchDataGood()
{
await Task.Delay(100);
throw new Exception("エラー!"); // 呼び出し元で catch できる
}
よくあるミス2: .Resultによるデッドロック
『.Result』や『.Wait()』はデッドロックを引き起こす可能性があります。特にASP.NET等の同期コンテキストがある環境では危険です。
// NG: .Result や .Wait() はデッドロックを引き起こす可能性がある var result = FetchDataGood().Result; // デッドロックの危険あり
// OK: 常に await を使う var result = await FetchDataGood();
よくあるミス3: awaitの付け忘れ
『await』を付け忘れるとタスクが完了を待たずに次の処理に進みます。コンパイラが CS4014 警告を出します。
missing_await_ng.cs
// NG: await を付け忘れるとタスクが完了を待たずに次の処理に進む
async Task Main()
{
FetchDataGood(); // await なし:タスクが完了を待たない
Console.WriteLine("完了"); // データ取得前に表示される可能性がある
}
warning CS4014: この呼び出しは待機されなかったため、現在のメソッドの実行は呼び出しの完了を待たずに続行されます 完了
// OK: 必ず await を付ける
async Task Main()
{
await FetchDataGood();
Console.WriteLine("完了");
}
概要
『async/await』を使うと、スレッドをブロックせずに時間のかかる処理(ネットワーク・ファイル I/O など)の完了を待てます。『await』の時点でスレッドが解放され、完了後に残りのコードが再開されます。
『async void』はイベントハンドラー以外では使わないでください。イベントハンドラー以外で async void を使うと、例外が呼び出し元に伝播できず、アプリケーションがクラッシュする可能性があります。通常は『async Task』を返すことで例外を呼び出し元に伝播できます。
複数の非同期処理を並列で実行したい場合は『Task.Run() / Task.Delay() / Task.WhenAll()』を参照してください。非同期メソッド内の例外処理は『例外処理と非同期』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。