C#のawaitとは?asyncとの違い・使い方・よくあるエラーを初心者向けに解説
はじめに
C#で非同期処理を学び始めると、必ず出てくるのがawaitとasyncです。
HTTP通信、ファイル読み込み、データベースアクセス、待機処理など、時間がかかる処理を書くときにawaitはよく使われます。しかし初心者のうちは、
「awaitを書くと何が起きるの?」
「asyncとの違いは?」
「awaitしないとどうなる?」
「ResultやWaitとは何が違うの?」
といった疑問を持ちやすいです。
この記事では、C#のawaitについて、基本的な意味から使い方、asyncとの違い、よくあるエラー、注意点まで初心者向けに解説します。
1. C#のawaitとは?初心者向けにわかりやすく解説
1-1. awaitは非同期処理の完了を待つためのキーワード
C#のawaitは、非同期処理の完了を待つためのキーワードです。
たとえば、次のような処理はすぐに終わらないことがあります。
C#await Task.Delay(1000);
このコードは「1秒待つ」という非同期処理の完了を待っています。
もう少し実用的な例では、Web APIからデータを取得する処理があります。
C#string result = await httpClient.GetStringAsync("https://example.com");
GetStringAsyncはHTTP通信を行う非同期メソッドです。awaitを付けることで、通信が完了して結果が返ってくるまで待ち、その結果をresultに代入できます。
1-2. awaitを使うと処理は止まるのか?
awaitを使うと、そのメソッド内の処理は一時停止します。
ただし、アプリケーション全体が完全に止まるわけではありません。
たとえば、UIアプリでボタンを押して時間のかかる処理を実行した場合でも、awaitを使って非同期処理にしておけば、画面が固まりにくくなります。
C#private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(3000);
label.Text = "完了しました";
}
この場合、await Task.Delay(3000)のところでメソッドの続きは一時停止しますが、UIスレッドを完全に占有し続けるわけではありません。
そのため、待っている間も画面操作が可能になりやすいです。
1-3. awaitが必要になる代表的な場面
awaitがよく使われるのは、時間のかかる処理です。
代表的な例は次のとおりです。
C#await Task.Delay(1000);
await httpClient.GetStringAsync(url);
await File.ReadAllTextAsync(path);
await stream.WriteAsync(buffer);
await database.SaveChangesAsync();
特に、外部とのやり取りが発生する処理ではawaitがよく使われます。
HTTP通信、ファイル操作、データベース処理などは、完了までに時間がかかる可能性があります。こうした処理を同期的に待つとアプリケーションが固まりやすくなるため、awaitを使って非同期的に待つのが一般的です。
1-4. 同期処理と非同期処理の違い
同期処理は、処理が終わるまで次の処理に進みません。
C#Thread.Sleep(3000);
Console.WriteLine("完了");
このコードでは、3秒間スレッドがブロックされます。その間、そのスレッドは他の処理を実行できません。
一方、非同期処理では次のように書けます。
C#await Task.Delay(3000);
Console.WriteLine("完了");
この場合、3秒待つという意味は同じですが、スレッドを無駄に占有し続けにくくなります。
つまり、同期処理は「終わるまでその場で待つ」、非同期処理は「完了を待ちながら、スレッドを有効活用しやすい」と考えると理解しやすいです。
2. asyncとawaitの違い
2-1. asyncはメソッドを非同期にするための修飾子
asyncは、メソッドの中でawaitを使えるようにするための修飾子です。
C#public async Task DoWorkAsync()
{
await Task.Delay(1000);
}
この例では、DoWorkAsyncメソッドにasyncが付いています。
asyncを付けることで、そのメソッド内でawaitを使用できます。
ただし、asyncを付けただけで自動的に別スレッドになるわけではありません。asyncは「このメソッドでは非同期処理を扱います」という宣言に近いものです。
2-2. awaitは非同期処理の結果を待つためのキーワード
awaitは、TaskやTask<T>などの非同期処理が完了するのを待つキーワードです。
C#public async Task<string> GetMessageAsync()
{
string message = await LoadMessageAsync();
return message;
}
この例では、LoadMessageAsyncの完了をawaitで待ち、返ってきた文字列をmessageに代入しています。
asyncはメソッド側に付けるもの、awaitは非同期処理を呼び出す場所で使うものです。
2-3. asyncだけ・awaitだけでは使えない理由
asyncだけを書いても、メソッド内にawaitがなければ非同期処理としての意味は薄くなります。
C#public async Task DoWorkAsync()
{
Console.WriteLine("処理中");
}
このコードはコンパイルできますが、awaitがないため警告が出ることがあります。
一方で、awaitだけを通常のメソッド内で使うことはできません。
C#public void DoWork()
{
await Task.Delay(1000); // エラー
}
awaitを使うには、基本的にメソッドにasyncを付ける必要があります。
正しくは次のように書きます。
C#public async Task DoWorkAsync()
{
await Task.Delay(1000);
}
2-4. async/awaitをセットで使う基本イメージ
asyncとawaitは、基本的にセットで使うと考えるとわかりやすいです。
C#public async Task SampleAsync()
{
Console.WriteLine("開始");
await Task.Delay(1000);
Console.WriteLine("終了");
}
このコードでは、SampleAsyncが非同期メソッドであり、Task.Delay(1000)の完了をawaitで待っています。
処理の流れは次のようになります。
C#開始
1秒待つ
終了
見た目は上から順番に実行される普通のコードに近いですが、内部では非同期処理として扱われます。これがasync/awaitの大きな特徴です。
3. C#でawaitを使う基本構文
3-1. awaitの基本的な書き方
awaitの基本構文は次のとおりです。
C#await 非同期メソッド();
戻り値がある場合は、次のように受け取れます。
C#var result = await 非同期メソッド();
具体例は次のとおりです。
C#await Task.Delay(1000);
C#string text = await File.ReadAllTextAsync("sample.txt");
awaitできる代表的な型はTaskとTask<T>です。
Taskは戻り値がない非同期処理、Task<T>は戻り値がある非同期処理を表します。
3-2. Taskを返すメソッドでawaitを使う例
戻り値がない非同期メソッドでは、Taskを返します。
C#public async Task SaveAsync()
{
await Task.Delay(1000);
Console.WriteLine("保存しました");
}
このメソッドを呼び出す側では、次のようにawaitします。
C#await SaveAsync();
Taskを返すメソッドは「処理の完了」を待つことができます。
戻り値はありませんが、awaitすることで「保存が終わってから次に進む」という流れを作れます。
3-3. Task<T>を返すメソッドで戻り値を受け取る例
戻り値がある非同期メソッドでは、Task<T>を使います。
C#public async Task<string> GetNameAsync()
{
await Task.Delay(1000);
return "Taro";
}
呼び出し側では、awaitするとTask<string>の中身であるstringを受け取れます。
C#string name = await GetNameAsync();
Console.WriteLine(name);
Task<T>は「将来的にT型の値を返す処理」と考えると理解しやすいです。
この場合、Task<string>は「将来的に文字列を返す非同期処理」です。
3-4. asyncメソッドの戻り値はTask・Task<T>・voidのどれにするべきか
asyncメソッドの戻り値には、主に次の3つがあります。
C#public async Task DoWorkAsync()
{
await Task.Delay(1000);
}
C#public async Task<int> GetCountAsync()
{
await Task.Delay(1000);
return 10;
}
C#public async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
}
基本的には、戻り値がない場合はTask、戻り値がある場合はTask<T>を使います。
async voidは、主にイベントハンドラーで使います。
たとえば、ボタンクリックの処理ではvoidが求められるため、async voidを使うことがあります。
C#private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
}
ただし、自分で作る通常の非同期メソッドでは、async voidではなくasync Taskを使うのが基本です。
4. awaitの具体的な使い方をコード例で解説
4-1. Task.Delayでawaitの動きを確認する
awaitの動きを確認するには、Task.Delayがわかりやすいです。
C#public async Task RunAsync()
{
Console.WriteLine("開始");
await Task.Delay(2000);
Console.WriteLine("2秒後に表示");
}
このコードでは、最初に「開始」と表示されます。
その後、2秒待ってから「2秒後に表示」と表示されます。
Thread.Sleepとの違いは、Task.Delayは非同期的に待てる点です。
C#Thread.Sleep(2000);
Thread.Sleepは現在のスレッドをブロックします。
C#await Task.Delay(2000);
await Task.Delayは、非同期処理として待機できるため、UIアプリやサーバーアプリで扱いやすいです。
4-2. HTTP通信でawaitを使う例
HTTP通信では、HttpClientの非同期メソッドをawaitすることがよくあります。
C#using System.Net.Http;
public async Task<string> GetHtmlAsync()
{
using var httpClient = new HttpClient();
string html = await httpClient.GetStringAsync("https://example.com");
return html;
}
GetStringAsyncは、指定したURLから文字列を取得する非同期メソッドです。
通信はすぐに終わるとは限りません。サーバーの応答待ちやネットワークの遅延があるため、awaitを使って非同期的に完了を待ちます。
戻り値を使う場合は、次のように書けます。
C#string html = await GetHtmlAsync();
Console.WriteLine(html);
4-3. ファイル読み込みでawaitを使う例
ファイル読み込みにも非同期メソッドがあります。
C#public async Task<string> ReadFileAsync(string path)
{
string text = await File.ReadAllTextAsync(path);
return text;
}
呼び出し側は次のようになります。
C#string text = await ReadFileAsync("sample.txt");
Console.WriteLine(text);
大きなファイルを読み込む場合、同期的に読み込むと処理が止まったように見えることがあります。
非同期メソッドを使うことで、アプリケーションの応答性を保ちやすくなります。
4-4. 複数の非同期処理を順番にawaitする例
複数の非同期処理を順番に実行したい場合は、上から順にawaitします。
C#public async Task RunSequentialAsync()
{
await Task.Delay(1000);
Console.WriteLine("1つ目の処理が完了");
await Task.Delay(1000);
Console.WriteLine("2つ目の処理が完了");
await Task.Delay(1000);
Console.WriteLine("3つ目の処理が完了");
}
この場合、合計で約3秒かかります。
それぞれの処理が前の処理の結果に依存している場合は、順番にawaitする書き方が適しています。
たとえば、ログインしてからユーザー情報を取得し、その後に注文履歴を取得するような処理です。
C#var token = await LoginAsync();
var user = await GetUserAsync(token);
var orders = await GetOrdersAsync(user.Id);
4-5. Task.WhenAllで並列実行する例
互いに依存しない非同期処理は、Task.WhenAllを使ってまとめて待つことができます。
C#public async Task RunParallelAsync()
{
Task task1 = Task.Delay(1000);
Task task2 = Task.Delay(1000);
Task task3 = Task.Delay(1000);
await Task.WhenAll(task1, task2, task3);
Console.WriteLine("すべて完了");
}
この場合、3つの処理を順番に待つのではなく、同時に開始してまとめて待ちます。
そのため、約1秒で完了します。
戻り値がある場合も同じように使えます。
C#public async Task RunParallelWithResultAsync()
{
Task<string> task1 = GetDataAsync("A");
Task<string> task2 = GetDataAsync("B");
string[] results = await Task.WhenAll(task1, task2);
foreach (var result in results)
{
Console.WriteLine(result);
}
}
ただし、処理の順番に意味がある場合や、前の結果を次の処理で使う場合は、無理にTask.WhenAllを使うべきではありません。
5. awaitを使うメリット
5-1. UIやアプリケーションが固まりにくくなる
awaitを使う大きなメリットは、UIやアプリケーションが固まりにくくなることです。
たとえば、Windows FormsやWPFなどのデスクトップアプリでは、UIスレッドが長時間ブロックされると画面が反応しなくなります。
悪い例は次のようなコードです。
C#private void Button_Click(object sender, EventArgs e)
{
Thread.Sleep(5000);
label.Text = "完了";
}
このコードでは、5秒間UIが固まる可能性があります。
awaitを使うと次のように書けます。
C#private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(5000);
label.Text = "完了";
}
この場合、待機中もUIスレッドを占有し続けにくいため、画面の応答性を保ちやすくなります。
5-2. 時間のかかる処理を効率よく扱える
HTTP通信、ファイル読み込み、データベースアクセスなどは、処理時間の多くが「待ち時間」になることがあります。
たとえば、Web APIの応答を待っている間、CPUはずっと計算しているわけではありません。
awaitを使うと、このような待ち時間を効率よく扱えます。
C#var user = await GetUserAsync();
var orders = await GetOrdersAsync(user.Id);
このように書くことで、時間のかかる処理でも読みやすく、安全に扱いやすくなります。
5-3. コールバックより読みやすいコードを書ける
async/awaitがない場合、非同期処理はコールバックで書くことが多くなります。
コールバックが増えると、処理の流れが追いにくくなります。
awaitを使うと、非同期処理でも上から下に読むような自然なコードを書けます。
C#public async Task ProcessAsync()
{
var user = await GetUserAsync();
var orders = await GetOrdersAsync(user.Id);
await SendMailAsync(user.Email, orders);
}
このコードは非同期処理ですが、見た目は通常の同期処理に近いです。
そのため、処理の順番や依存関係を理解しやすくなります。
5-4. 例外処理をtry-catchで書きやすい
awaitを使うと、非同期処理の例外もtry-catchで扱いやすくなります。
C#public async Task LoadAsync()
{
try
{
string text = await File.ReadAllTextAsync("sample.txt");
Console.WriteLine(text);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("ファイルが見つかりません");
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("エラーが発生しました");
Console.WriteLine(ex.Message);
}
}
awaitした非同期処理で例外が発生した場合、その場所で例外を捕まえることができます。
これにより、同期処理に近い感覚でエラーハンドリングを書けます。
6. awaitでよくあるエラーと解決方法
6-1. 「awaitはasyncメソッド内でのみ使用できます」の原因と対処法
初心者がよく見るエラーに、次のようなものがあります。
C#The 'await' operator can only be used within an async method
日本語では「await演算子はasyncメソッド内でのみ使用できます」という意味です。
原因は、asyncが付いていないメソッド内でawaitを使っていることです。
悪い例です。
C#public void DoWork()
{
await Task.Delay(1000);
}
修正するには、メソッドにasyncを付け、戻り値をTaskにします。
C#public async Task DoWorkAsync()
{
await Task.Delay(1000);
}
イベントハンドラーの場合は、例外的にasync voidを使うことがあります。
C#private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
}
通常のメソッドでは、できるだけasync Taskを使いましょう。
6-2. 「Taskをawaitしていない」場合に起きる問題
非同期メソッドを呼び出したのにawaitしないと、処理の完了を待たずに次へ進みます。
C#SaveAsync();
Console.WriteLine("保存完了");
このコードでは、SaveAsyncの完了前に「保存完了」と表示される可能性があります。
正しくは次のように書きます。
C#await SaveAsync();
Console.WriteLine("保存完了");
また、awaitしないと例外を適切に扱えないことがあります。
C#try
{
SaveAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
このコードでは、SaveAsync内で発生した例外を期待どおりに捕まえられない可能性があります。
正しくは次のようにします。
C#try
{
await SaveAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
意図的に待たない場合もありますが、初心者のうちは基本的にTaskを返す非同期メソッドはawaitする、と覚えておくと安全です。
6-3. async voidを使ってはいけないケース
async voidは、基本的にイベントハンドラー以外では避けるべきです。
悪い例です。
C#public async void SaveAsync()
{
await Task.Delay(1000);
throw new Exception("エラー");
}
async voidは呼び出し側でawaitできません。
C#await SaveAsync(); // できない
そのため、完了を待つことも、例外を適切に扱うことも難しくなります。
正しくはTaskを返します。
C#public async Task SaveAsync()
{
await Task.Delay(1000);
throw new Exception("エラー");
}
呼び出し側では次のようにできます。
C#try
{
await SaveAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
async voidは、ボタンクリックなど戻り値がvoidに決まっているイベントハンドラーで使うもの、と考えるとよいです。
6-4. ResultやWaitでデッドロックする原因
非同期処理を同期的に待つ方法として、.Resultや.Wait()があります。
C#var result = GetDataAsync().Result;
C#GetDataAsync().Wait();
しかし、UIアプリやASP.NETの一部の環境では、これらがデッドロックの原因になることがあります。
デッドロックとは、処理同士が互いに待ち合ってしまい、先に進めなくなる状態です。
基本的には、非同期メソッドはawaitで待つようにしましょう。
悪い例です。
C#string result = GetMessageAsync().Result;
良い例です。
C#string result = await GetMessageAsync();
async/awaitを使う場合は、呼び出し元まで非同期にしていくのが基本です。
C#public async Task RunAsync()
{
string result = await GetMessageAsync();
Console.WriteLine(result);
}
6-5. ConfigureAwaitとは何か
ConfigureAwaitは、await後に元のコンテキストへ戻るかどうかを指定するためのものです。
C#await SomeAsync().ConfigureAwait(false);
通常、UIアプリではawaitの後にUIスレッドへ戻る必要があります。
C#private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
label.Text = "完了";
}
この例では、await後にUIを更新するため、元のUIスレッドに戻る必要があります。
一方、ライブラリコードなどでUI更新が不要な場合は、次のように書くことがあります。
C#await SomeAsync().ConfigureAwait(false);
初心者のうちは、アプリケーションコードでは無理にConfigureAwait(false)を使う必要はありません。
ただし、ライブラリや共通部品を作るときには、パフォーマンスやデッドロック回避の観点で使われることがあります。
7. awaitを使うときの注意点
7-1. awaitを書いた場所で処理の流れが変わる
awaitを書くと、その非同期処理が完了するまで、そのメソッドの続きは実行されません。
C#Console.WriteLine("A");
await Task.Delay(1000);
Console.WriteLine("B");
このコードでは、Aが表示され、1秒後にBが表示されます。
一方、awaitせずにTaskを開始すると、処理の流れが変わります。
C#Task task = Task.Delay(1000);
Console.WriteLine("A");
await task;
Console.WriteLine("B");
この場合、Task.Delayは先に開始され、その後でAが表示されます。
awaitを書く場所によって、処理の開始タイミングや待機タイミングが変わるため注意しましょう。
7-2. 何でもasyncにすればよいわけではない
async/awaitは便利ですが、すべてのメソッドを非同期にすればよいわけではありません。
たとえば、単純な計算だけの処理にasyncを付ける必要はありません。
C#public int Add(int a, int b)
{
return a + b;
}
このような処理を無理に非同期にする必要はありません。
C#public async Task<int> AddAsync(int a, int b)
{
return a + b;
}
このコードは非同期処理らしい待機がなく、asyncにするメリットがほとんどありません。
awaitは、主に時間のかかるI/O処理や非同期APIを使う場面で活用します。
7-3. 非同期処理とマルチスレッドの違い
awaitを使うと、必ず別スレッドで処理されると思われがちですが、これは正確ではありません。
非同期処理は「待ち時間を効率よく扱う仕組み」です。
一方、マルチスレッドは「複数のスレッドで処理を並行して実行する仕組み」です。
たとえば、HTTP通信の待機中は、必ずしも専用のスレッドがずっと動いているわけではありません。
C#string result = await httpClient.GetStringAsync(url);
このコードは非同期ですが、「別スレッドでCPU計算をしている」という意味ではありません。
CPUを使う重い処理を別スレッドで実行したい場合は、Task.Runを使うことがあります。
C#int result = await Task.Run(() => HeavyCalculation());
7-4. 例外処理はawaitする場所で行う
非同期処理の例外は、awaitしたタイミングで表面化します。
C#Task task = ErrorAsync();
try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
この場合、ErrorAsync内で発生した例外は、await taskの場所で捕まえられます。
そのため、例外処理をしたい場合は、awaitをtry-catchの中に入れることが重要です。
C#try
{
await ErrorAsync();
}
catch (Exception ex)
{
Console.WriteLine("エラーを捕まえました");
Console.WriteLine(ex.Message);
}
逆に、awaitしないまま放置すると、例外に気づきにくくなることがあります。
7-5. パフォーマンスを意識したawaitの使い方
awaitは便利ですが、使い方によっては無駄な待ち時間が増えることがあります。
たとえば、互いに依存しない処理を順番にawaitすると時間がかかります。
C#var user = await GetUserAsync();
var news = await GetNewsAsync();
var weather = await GetWeatherAsync();
これらが互いに依存しない場合は、先にタスクを開始してからTask.WhenAllで待つ方が効率的です。
C#Task<User> userTask = GetUserAsync();
Task<News> newsTask = GetNewsAsync();
Task<Weather> weatherTask = GetWeatherAsync();
await Task.WhenAll(userTask, newsTask, weatherTask);
User user = await userTask;
News news = await newsTask;
Weather weather = await weatherTask;
ただし、並列に実行しすぎると、サーバーやネットワークに負荷をかけることもあります。
awaitは、処理の依存関係や負荷を考えながら使うことが大切です。
8. awaitを使うべきケース・使わないケース
8-1. awaitを使うべき処理の例
awaitを使うべき代表的なケースは、時間のかかるI/O処理です。
たとえば、次のような処理です。
C#await httpClient.GetStringAsync(url);
await File.ReadAllTextAsync(path);
await stream.WriteAsync(buffer);
await dbContext.SaveChangesAsync();
await Task.Delay(1000);
Web APIへのアクセス、ファイルの読み書き、データベース処理、ネットワーク通信などは、awaitとの相性がよいです。
これらの処理は、CPUで計算し続けるというより、外部の応答や入出力の完了を待つ時間が長くなりやすいためです。
8-2. awaitを使わなくてもよい処理の例
逆に、すぐに終わる単純な処理ではawaitは不要です。
C#int total = price * count;
C#string message = "Hello";
C#var list = new List<int> { 1, 2, 3 };
このような処理を無理に非同期化してもメリットはほとんどありません。
また、すでにメモリ上にあるデータを加工するだけなら、通常は同期処理で十分です。
C#var names = users.Select(x => x.Name).ToList();
awaitを使うかどうかは、「その処理に待ち時間があるか」「非同期APIが用意されているか」を基準に考えるとよいです。
8-3. CPUバウンド処理とI/Oバウンド処理の違い
非同期処理を理解するうえで重要なのが、CPUバウンド処理とI/Oバウンド処理の違いです。
CPUバウンド処理は、CPUの計算能力を多く使う処理です。
C#int result = HeavyCalculation();
画像処理、大量データの計算、暗号化、圧縮処理などが該当します。
一方、I/Oバウンド処理は、入出力の待ち時間が中心になる処理です。
C#string text = await File.ReadAllTextAsync(path);
HTTP通信、ファイルアクセス、データベースアクセスなどが該当します。
awaitが特に効果を発揮するのは、I/Oバウンド処理です。
CPUバウンド処理を非同期にしたい場合は、Task.Runを使って別スレッドに逃がすことを検討します。
8-4. Task.Runとawaitの使い分け
Task.Runは、処理をスレッドプール上で実行したいときに使います。
C#int result = await Task.Run(() => HeavyCalculation());
これは、重い計算処理をUIスレッドから切り離したい場合などに使われます。
一方、HTTP通信やファイル読み込みなど、もともと非同期メソッドが用意されている場合は、Task.Runで包む必要はありません。
悪い例です。
C#string html = await Task.Run(() => httpClient.GetStringAsync(url));
このようにするより、直接awaitします。
C#string html = await httpClient.GetStringAsync(url);
基本的な使い分けは次のとおりです。
I/Oバウンド処理では、用意されている非同期メソッドをawaitします。
CPUバウンド処理では、必要に応じてTask.Runを使います。
9. C#のawaitに関するよくある質問
9-1. awaitを使うと別スレッドで実行される?
awaitを使ったからといって、必ず別スレッドで実行されるわけではありません。
awaitは、非同期処理の完了を待つための仕組みです。
たとえば、次のコードはHTTP通信の完了を待っています。
C#string result = await httpClient.GetStringAsync(url);
これは「別スレッドでずっと処理している」というより、「通信の完了を非同期的に待っている」と考える方が正確です。
別スレッドで重い処理を実行したい場合は、Task.Runを使います。
C#int result = await Task.Run(() => HeavyCalculation());
9-2. awaitしないとどうなる?
awaitしない場合、その非同期処理の完了を待たずに次の処理へ進みます。
C#SaveAsync();
Console.WriteLine("次の処理");
この場合、SaveAsyncが終わる前に「次の処理」が実行される可能性があります。
また、例外を適切に捕まえられないこともあります。
基本的には、Taskを返す非同期メソッドはawaitしましょう。
C#await SaveAsync();
Console.WriteLine("次の処理");
ただし、ログ送信など「完了を待たなくてもよい」と明確に判断できる処理では、意図的にawaitしないこともあります。その場合でも、例外処理やログ出力などをきちんと考慮する必要があります。
9-3. awaitは戻り値がない処理にも使える?
はい、戻り値がない処理にもawaitは使えます。
戻り値がない非同期処理は、通常Taskを返します。
C#public async Task SaveAsync()
{
await Task.Delay(1000);
Console.WriteLine("保存しました");
}
呼び出し側では次のように書きます。
C#await SaveAsync();
この場合、戻り値はありませんが、処理が完了するまで待つことができます。
戻り値がある場合はTask<T>を使います。
C#public async Task<string> GetNameAsync()
{
await Task.Delay(1000);
return "Taro";
}
9-4. Mainメソッドでawaitは使える?
C#では、Mainメソッドをasyncにしてawaitを使うことができます。
C#public static async Task Main(string[] args)
{
await Task.Delay(1000);
Console.WriteLine("完了");
}
コンソールアプリで非同期処理を使いたい場合は、この書き方が便利です。
古い書き方では、Mainで直接awaitできないため、別の非同期メソッドを呼び出して.Wait()するようなコードが使われることもありました。
C#public static void Main(string[] args)
{
MainAsync().Wait();
}
public static async Task MainAsync()
{
await Task.Delay(1000);
}
しかし、現在はasync Task Mainを使える環境であれば、そちらを使う方が自然です。
9-5. async/awaitはUnityでも使える?
UnityでもC#のasync/awaitは使えます。
ただし、Unityでは従来からコルーチンもよく使われます。
C#IEnumerator WaitCoroutine()
{
yield return new WaitForSeconds(1);
Debug.Log("完了");
}
async/awaitを使うと、次のような書き方ができます。
C#async Task WaitAsync()
{
await Task.Delay(1000);
Debug.Log("完了");
}
ただし、Unityのメインスレッドでしか操作できないAPIもあるため、スレッドや実行タイミングには注意が必要です。
Unityでは、コルーチン、async/await、UniTaskなどを用途に応じて使い分けることが多いです。
初心者の場合は、まずUnity標準のコルーチンを理解し、そのうえでasync/awaitを学ぶとよいでしょう。
まとめ
C#のawaitは、非同期処理の完了を待つためのキーワードです。
asyncはメソッドに付ける修飾子で、awaitは非同期処理を待つ場所で使います。
基本的な形は次のとおりです。
C#public async Task SampleAsync()
{
await Task.Delay(1000);
}
戻り値がない非同期メソッドはTask、戻り値がある非同期メソッドはTask<T>を返します。
C#public async Task SaveAsync()
{
await Task.Delay(1000);
}
C#public async Task<string> LoadAsync()
{
await Task.Delay(1000);
return "完了";
}
awaitを使うことで、HTTP通信、ファイル読み込み、データベース処理など、時間のかかる処理を効率よく扱えます。
また、非同期処理でも上から下に読むようなコードを書けるため、コールバックよりも可読性が高くなります。
一方で、async voidの乱用、await忘れ、.Resultや.Wait()によるデッドロックには注意が必要です。
C#で非同期処理を書くときは、まず次のポイントを押さえましょう。
awaitは非同期処理の完了を待つために使います。
awaitを使うメソッドには基本的にasyncを付けます。
通常の非同期メソッドはasync Taskまたはasync Task<T>にします。
Taskを返すメソッドは、基本的にawaitして完了や例外を扱います。
I/Oバウンド処理にはawait、CPUバウンド処理には必要に応じてTask.Runを使います。
awaitを正しく理解すると、C#で応答性が高く、読みやすい非同期コードを書けるようになります。

