> 作業効率UP!! オススメのモバイルモニターを紹介

【C#】非同期処理(async/await)の基本をマスターする

当ページのリンクには広告が含まれています。
  • URLをコピーしました!

この記事では非同期処理(async/await)について詳しく説明しています。

現在、多くのプログラミング言語で非同期処理を扱う機能としてasync/awaitが採用されています。C#も例外ではなく、現代の.NETアプリケーション開発においてasync/awaitは至る所で使用される重要な機能です。

本記事では、この非同期処理の基礎についてサンプルコードを交えながら紹介します。ぜひ参考にしてみてください。

オススメの参考書

C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。

記事の内容

同期処理と非同期処理の違い

まずは「同期」と「非同期」について説明します。

同期と非同期の違い

  • 同期処理はある処理の完了を待ってから次の処理を開始する
  • 非同期処理はある処理の完了を待たずに次の処理を開始する。

同期処理は、コードを上から下まで順番に処理を実行します。下記コードを例にすると、MethodAの処理が完了したらMethodBの処理が実行されます。

MethodA();
MethodB();

この時、「UIスレッド」と呼ばれるメインスレッド上で処理を実行します。時間のかかる処理を行うと、メインスレッドはその処理に独占されてしまうため、アプリ上で操作(クリックやプログレスバーの表示)ができません。これはアプリがフリーズしたように見えるため、ユーザーからするとストレスが溜まってしまう要因になります。

一方で、非同期処理はある処理が終わるのを待たずに、別の処理を実行します。下記コードを例にすると、MethodAの処理が実行されたら、すぐにMethodBの処理が実行されます。

Task.Runは処理を別スレッドで実行する、非同期処理でよく使用されるメソッドです。ラムダ式で記述できます。

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メソッドを使用する場合、複数の非同期処理が並列で実行され、全ての処理が完了するまで待機されます。非同期処理毎に終了するのを待つ必要がなくなり、効率的な並列処理を行えます。

例外処理を実装する

例外処理を行う際は、同期処理と同じようにtrycatchfinallyで行います。

// ボタンクリックのイベントハンドラ
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メソッドで例外が発生した際、trycatchで例外をキャッチすることができます。TaskAsyncメソッドの戻り値がvoidの場合、例外をキャッチすることができません。

まとめ

この記事では C# の非同期処理の基礎について紹介をしました。

async/awaitを使用することで、メインスレッドとは別スレッドで処理を実行できるようになります。ユーザーが操作している時(例えば、テキストボックスに文字を入力している時など)に別の処理を実行したい場合などには使えるでしょう。

非同期処理はデッドロックの回避やタスクの並列処理を可能します。プログラムのパフォーマンスを向上させることができる仕組みなので、ぜひ扱えるようになっておきましょう。

以上、最後まで読んでいただきありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
記事の内容