現在、多くのプログラミング言語で非同期処理を扱う機能としてasync/awaitが採用されています。C#も例外ではなく、現代の.NETアプリケーション開発においてasync/awaitは至る所で使用される重要な機能です。
本記事では、この非同期処理の基礎についてサンプルコードを交えながら紹介します。ぜひ参考にしてみてください。
オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
同期処理と非同期処理の違い
まずは「同期」と「非同期」について説明します。
同期と非同期の違い
- 同期処理はある処理の完了を待ってから次の処理を開始する
- 非同期処理はある処理の完了を待たずに次の処理を開始する。
同期処理は、コードを上から下まで順番に処理を実行します。下記コードを例にすると、MethodA
の処理が完了したらMethodB
の処理が実行されます。
MethodA();
MethodB();
この時、「UIスレッド」と呼ばれるメインスレッド上で処理を実行します。時間のかかる処理を行うと、メインスレッドはその処理に独占されてしまうため、アプリ上で操作(クリックやプログレスバーの表示)ができません。これはアプリがフリーズしたように見えるため、ユーザーからするとストレスが溜まってしまう要因になります。
一方で、非同期処理はある処理が終わるのを待たずに、別の処理を実行します。下記コードを例にすると、MethodA
の処理が実行されたら、すぐにMethodB
の処理が実行されます。
Task.Run(() => MethodA());
Task.Run(() => MethodB());
メインスレッドとは別のスレッドで処理を実行するため、アプリ上で操作が可能になります。
そのため、重たい処理を実行してもアプリがフリーズすることはありません。ユーザーは画面上のボタンをクリックすることができ、プログレスバーが画面に表示されていたら止まることなく動き続けるようになります。
非同期処理(async/await)について
「同期処理と非同期処理の違い」で説明した非同期処理のコードでTask.Run
を使用していましたが、このコードを実行するとMethodA
の処理を待たずにMethodB
が実行されます。
MethodA
を非同期で処理し、かつ処理が完了するまで待機したい場合に使用するのがasync
/await
です。
async
/await
は非同期で頻繁に使用するキーワードです。ここからはサンプルコードを交えながら説明をしていきます。
asyncとは
async
とは、メソッド内でawait
を使用できるようにするキーワードです。
非同期メソッドの戻り値がvoid
である場合、Task
にします。voidのままにしておくと、メソッド内で例外が発生した際にキャッチできず、エラーハンドリングが出来ません。
async Task メソッド名()
{
// 非同期処理
}
戻り値の型がvoid以外(stringやintなど)の場合は次のように記述します。
// メソッドの戻り値がstringの場合
async Task<string> メソッド名()
{
var result = string.Empty;
// 非同期処理
retune result
}
例外としてイベントハンドラーに対してのみ、voidを使用できます。
// イベントハンドラーの場合
async void メソッド名()
{
//非同期処理
}
awaitとは
await
とは、非同期処理が完了するまで待機するために使用されるキーワードです。待機する際、メソッドの呼び出し元であるスレッドに処理を戻し、非同期のタスクが完了したら、await
以降の処理を実行します。
await 式;
async/awaitの動作を理解する
async
/await
を使用したコードを例に動作フローを解説します。
// ボタンクリックのイベントハンドラ
async void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("ボタンの処理を開始"); // 処理1
await TaskAsync(); // 処理2
Debug.WriteLine("ボタンの処理を終了"); // 処理3
}
// 非同期タスク
async Task TaskAsync()
{
Debug.WriteLine("非同期タスクを開始");
// 非同期処理(重たい処理)
await Task.Delay(5000); // 5秒間待機
Debug.WriteLine("非同期タスクを終了");
}
このコードのスレッドのやり取りを図にすると以下になります。
ボタンをクリックするとイベントが発生し、Button_Click
イベントハンドラが呼び出されます。UIスレッド上で処理1であるDebug.WriteLine("ボタンの処理を開始")
が実行されます。
処理1が完了したら、別スレッド上で処理2であるTaskAsync()
が実行されます。このメソッドはawaitが付いているので、処理2が完了するまでUIスレッドは解放されるので、ユーザーは任意の操作が可能になります。
処理2が完了したら、UIスレッド上で処理3であるDebug.WriteLine("ボタンの処理を終了")
が実行されます。
コードを実行すると、以下のような結果になります。
ボタンの処理を開始
非同期タスクを開始
非同期タスクを終了
ボタンの処理を終了
このようにawaitがメソッド内にある場合は、スレッド間で処理が実行されます。メソッド内に複数のawaitがある場合、スレッド間のやり取りが繰り返し実行されることになります。
複数の非同期処理を実行する
先ほどの説明では1つの非同期を実行しましたが、Task.WhenAll
メソッドを使用することで複数の非同期処理を同時に実行することができます。複数の非同期処理を並列して実行できるので処理時間の短縮が見込めます。
// ボタンクリックのイベントハンドラ
async void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("ボタンの処理を開始"); // 処理1
var task1 = TaskAsync1(); // 処理2
var task2 = TaskAsync2(); // 処理3
await Task.WhenAll(task1, task2);
Debug.WriteLine("ボタンの処理を終了"); // 処理4
}
// 非同期タスク1
async Task TaskAsync1()
{
Debug.WriteLine("非同期タスク1を開始");
// 非同期処理(重たい処理のデモ)
await Task.Delay(5000); // 5秒間待機
Debug.WriteLine("非同期タスク1を終了");
}
// 非同期タスク2
async Task TaskAsync2()
{
Debug.WriteLine("非同期タスク2を開始");
// 非同期処理(重たい処理のデモ)
await Task.Delay(3000); // 3秒間待機
Debug.WriteLine("非同期タスク2を終了");
}
このコードのスレッドのやり取りを図にすると以下になります。
コードを実行すると、以下のような結果になります。
ボタンの処理を開始
非同期タスク1を開始
非同期タスク2を開始
非同期タスク2を終了
非同期タスク1を終了
ボタンの処理を終了
このようにTask.WhenAll
メソッドを使用する場合、複数の非同期処理が並列で実行され、全ての処理が完了するまで待機されます。非同期処理毎に終了するのを待つ必要がなくなり、効率的な並列処理を行えます。
例外処理を実装する
例外処理を行う際は、同期処理と同じようにtry
–catch
–finally
で行います。
// ボタンクリックのイベントハンドラ
async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
await TaskAsync();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
Debug.WriteLine("イベント終了");
}
}
// 非同期タスク
async Task TaskAsync()
{
await Task.Run(() => throw new Exception("例外発生"));
}
TaskAsync
メソッドで例外が発生した際、try
–catch
で例外をキャッチすることができます。TaskAsync
メソッドの戻り値がvoid
の場合、例外をキャッチすることができません。
まとめ
この記事では C# の非同期処理の基礎について紹介をしました。
async
/await
を使用することで、メインスレッドとは別スレッドで処理を実行できるようになります。ユーザーが操作している時(例えば、テキストボックスに文字を入力している時など)に別の処理を実行したい場合などには使えるでしょう。
非同期処理はデッドロックの回避やタスクの並列処理を可能します。プログラムのパフォーマンスを向上させることができる仕組みなので、ぜひ扱えるようになっておきましょう。
以上、最後まで読んでいただきありがとうございました。