前回は例外処理(try-catch)の基本を解説しました。
今回は、非同期処理(async/await)の基本や実務でのポイントを解説します。
非同期処理とは?
「非同期処理」という言葉は専門的に感じるかもしれませんが、日常生活でもよく似た場面があります。
例えば洗濯では、洗濯機に洗濯物を入れてスイッチを押したあと、洗濯が終わるまでずっと洗濯機の前で待つことはしないはずです。その間に他の家事や作業を進めるでしょう。
このように、「何かが終わるのを待っている間に、他のことも同時に行う」というのが、プログラミングにおける非同期処理の考え方とよく似ています。
なぜ非同期処理が必要なのか?
例えばWebアプリでは、ユーザーが「データを取得してほしい」とサーバーにリクエストを送ると、サーバーはデータベースから情報を読み込んだり、別のサービスと通信したりするので、どうしても時間がかかります。
もしアプリが「1つのリクエストが終わるまで、他の処理を止めて待つ」(=同期処理)という動きの場合、アクセスが増えると順番待ちの人がどんどん増えて、アプリ全体が遅くなってしまいます。
そこで活躍するのが非同期処理です。
「1つのリクエストが処理中の間でも、別のリクエストの処理を始める」ことができるので、ユーザーを待たせることなくリクエストに素早く応答できるようになります。
実務で非同期処理がよく使われる場面
実際の開発では、次のような処理で非同期処理がよく使われます。
- Web APIの呼び出し:天気予報や地図サービスなど外部APIからのデータ取得
- データベースとのやりとり:データの保存や取得など
- ファイルの読み書き:ファイルのアップロードやダウンロード
- メールの送信:お問い合わせや会員登録後の確認メール送信など
- 外部サービスとの連携:クラウドストレージや認証・認可サービスへのアクセスなど
このような時間がかかる処理を非同期にすることで、プログラムが他の仕事を同時に進められるようになり、アプリ全体の応答性や効率が大きく向上します。
asyncとawaitの基本(C#の非同期処理の書き方)
C#で非同期処理を実現するためには、「async(エイシンク、またはアシンク)」と「await(アウェイト)」という2つのキーワードを使います。
この2つはセットで覚えておきましょう。
非同期メソッドの基本構文
async/awaitを使うことで、C#の非同期処理を同期処理とほぼ同じような見た目で書くことができます。
非同期処理を行うには「非同期メソッド」を作ります。構文は次の通りです。
public async Task メソッド名()
{
// 非同期で行いたい処理
await 時間がかかる処理;
}
- async:非同期メソッドを作るときに必ず付けるキーワードです。
- Task型/Task型:戻り値は「Task」または「Task」にします。
- 値を返さない場合:
Task
(voidは原則使わない) - 値を返す場合:
Task<string>
やTask<int>
など
- 値を返さない場合:
- メソッド名:非同期メソッド名の末尾に「Async」を付けるのがC#の慣習となっています。
- await:時間がかかる処理(例:API呼び出し、DBアクセスなど)の完了を待つときに使います。awaitを使うことで、プログラムは「その間に他の処理を進める」ことができます。
非同期メソッドのサンプル
以下は、C#の非同期メソッドのシンプルな例です。
// メッセージを取得して文字列で返す非同期メソッド
public async Task<string> GetMessageAsync()
{
// 本来はAPIやDBからデータを取得するなど、時間がかかる処理を行う。
// 今回はサンプルなので「2秒間待つ」ことでその待ち時間を疑似的に再現。
await Task.Delay(2000);
return "メッセージ取得完了";
}
// GetMessageAsyncの結果をコンソールに表示する非同期メソッド
public async Task RunAsync()
{
// GetMessageAsyncの処理が完了するのを非同期で待つ
var result = await GetMessageAsync();
Console.WriteLine(result);
}
この例では、GetMessageAsync
メソッドの中で「2秒間待つ」処理をしています。
これは、実際にはAPIやデータベースからデータを取得する場面を想定した「疑似的な待ち時間」です。
Task.Delay
はサンプルで使っているだけで、実際の現場では「APIやDBアクセス」など時間のかかる処理がこの場所に入ります。
非同期処理の注意点
ここまででC#の非同期メソッドの基本的な書き方や使い方を紹介しましたが、非同期処理は、ちょっとした書き方のミスでトラブルが発生しやすい分野です。
ここでは初心者の方がよくつまずくポイントや、実務で気をつけたい注意点を紹介します。
awaitの付け忘れ
非同期メソッドを呼び出すとき、「await」を付け忘れると、意図した動作にならないだけでなく、思わぬバグの原因になることがあります。
たとえば次のようなコードを見てみましょう。
// よくある失敗例(awaitを付けていない)
try
{
SomeAsyncMethod();
}
catch (Exception ex)
{
// ここでは例外が取れない
}
この場合、非同期メソッドの中でエラーが発生しても、catch文で例外をキャッチできません。
正しくは次のように、awaitを付けて呼び出します。
try
{
await SomeAsyncMethod();
}
catch (Exception ex)
{
Console.WriteLine($"エラー発生: {ex.Message}");
}
awaitを使うことで、非同期メソッド内の例外もしっかりキャッチできるようになります。
awaitの書き忘れには他にもデメリットがあり、非同期処理の完了を待たずに次の処理が進んでしまったり、Taskが「投げっぱなし」になってリソースが正しく解放されないこともあります。
こうした理由から、非同期メソッドを呼び出すときは必ずawaitを付けることを徹底しましょう。
async voidは使わない
もう一つ注意したいのが、非同期メソッドをasync void
で定義してしまうことです。
// 悪い例
public async void DoSomethingAsync()
{
// ...
}
この書き方だと、非同期処理中にエラーが発生してもcatchできなかったり、プログラムが予期せず終了してしまうことがあります。
基本的にはTask
やTask<T>
を使い、async void
はイベントハンドラなど特別な場合だけにしましょう。
複数の非同期処理を同時に実行する(Task.WhenAllの使い方)
C#の非同期処理は、「一度に1つずつ」だけでなく、複数の非同期メソッドを同時に並行して実行することもできます。
例えば「いくつかのWeb APIからデータをまとめて取得したい」「複数のファイルを一気にダウンロードしたい」といった場面でとても役立ちます。
このとき便利なのが、Task.WhenAll
という仕組みです。
Task.WhenAllを使うことで、複数の非同期処理を同時に開始して、全部が終わるのをまとめて待つことができます。
Task.WhenAllの基本的な使い方
以下は、2つのAPIからデータを取得し、両方が完了するのを待つC#の非同期メソッド例です。
public async Task GetMultipleDataAsync()
{
// 2つの非同期処理を同時にスタート
var task1 = FetchDataFromApi1Async();
var task2 = FetchDataFromApi2Async();
// 両方のAPI呼び出しが完了するまで待つ
await Task.WhenAll(task1, task2);
// 結果を取得して出力
var result1 = await task1;
var result2 = await task2;
Console.WriteLine(result1);
Console.WriteLine(result2);
}
このように、「順番に1つずつ待つ」のではなく「全部を同時にスタートして、すべてが終わるのを待つ」ことで、プログラム全体の処理時間を短縮することができます。
実務でよくある活用例
- 複数の外部APIやWebサービスからデータを集める
- たくさんのファイルや画像を一括でダウンロード/アップロードする
- データベースから複数のテーブルを同時に読み込む
このような「複数の非同期処理を並列で実行したい」ときには、C#のTask.WhenAll
がとても便利です。
まとめ
C#入門文法シリーズの最終回として、今回は「非同期処理(async/await)」の基本や実践的な使い方を解説しました。
非同期処理は、Webアプリや業務システム開発で必ず役立つ技術です。まずは基本の書き方や注意点をおさえて、少しずつ自分のアプリで試してみましょう。
シリーズの他の記事では、「C#の基礎」や「LINQ」「例外処理」「クラス設計」など実務で使える内容を網羅しています。
ぜひ過去の記事も参考にしてみてください。
