非同期プログラミング入門 – C#での実装方法

C#では、async/awaitを使うことで、待機処理やバックグラウンドでの処理を簡単に実装できます。ここでは、非同期処理の基本的な考え方と、実際にコードを書くときのポイントについて解説します。


1. 非同期処理の基本概念

  • 非同期処理とは:
    長い時間がかかる処理(例えば、ネットワーク通信やファイルアクセス)を実行中も、他の作業をブロックせずに並行して行える仕組みです。
  • ブロックしない待機:
    Task.Delayのような非同期メソッドを利用すると、指定した時間だけ処理を「待つ」間も、他の作業を進めることができます。

2. async/awaitを使った非同期メソッド

基本の書き方

非同期メソッドを定義するには、asyncキーワードを使い、返り値にTaskまたはTask<T>を指定します。
以下の例は、1秒ごとに繰り返し処理を行う非同期メソッドです。

using System;
using System.Threading.Tasks;

class Program
{
    // Taskを返す非同期メソッド
    static async Task ProcessWithDelayAsync()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Iteration {i}: 処理を実行中...");
            await Task.Delay(1000); // 1秒待機
        }
        Console.WriteLine("非同期処理完了");
    }
}

メソッドのポイント

  • async修飾子:
    このキーワードを付けることで、メソッド内でawaitを使用できるようになります。
  • awaitキーワード:
    Task.Delayなどの非同期メソッドの完了を待機しながら、スレッドをブロックしません。

3. 戻り値の型と呼び出し方

async Task と async void

  • async Task:
    • 戻り値としてTaskを返します。
    • 呼び出し元でawaitを利用して完了待ちができ、例外もキャッチしやすいです。
  static async Task ProcessWithDelayAsync()
  {
      // 省略
  }
  • async void:
    • 戻り値がなく、呼び出し元で待機できません。
    • 主にイベントハンドラーなど、戻り値を必要としない場面で使用します。
  static async void ProcessWithDelayVoid()
  {
      // 省略
  }

呼び出し方の違い

非同期メソッドとして呼び出す

戻り値がTaskの場合、呼び出し元でawaitを使って待機するのが基本です。

static async Task CallerMethodAsync()
{
    Console.WriteLine("CallerMethodAsync 開始");
    await ProcessWithDelayAsync();  // 非同期処理の完了を待機
    Console.WriteLine("CallerMethodAsync 完了");
}

static async Task Main(string[] args)
{
    await CallerMethodAsync();
}

同期的に呼び出す方法

非同期メソッドを同期的に呼び出す場合、返されたTaskに対して.Wait().GetAwaiter().GetResult()を利用します。ただし、UIスレッドなどで使用するとデッドロックのリスクがあるので注意してください。

static void CallerMethod()
{
    ProcessWithDelayAsync().Wait();  // 同期的に待機
    // または
    // ProcessWithDelayAsync().GetAwaiter().GetResult();
    Console.WriteLine("CallerMethod 完了");
}

static void Main(string[] args)
{
    CallerMethod();
}

async void の呼び出し

async voidの場合は、呼び出し側で待機することができないため、単に呼び出すだけになります。例外の捕捉も呼び出し側では行いにくいので、注意が必要です。

static async void ProcessWithDelayVoid()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"Iteration {i}: 処理を実行中...");
        await Task.Delay(1000);
    }
    Console.WriteLine("非同期処理完了");
}

static void CallerMethodVoid()
{
    ProcessWithDelayVoid();
    Console.WriteLine("CallerMethodVoid 呼び出し完了");
}

static void Main(string[] args)
{
    CallerMethodVoid();
}

4. 呼び出し元での await の必要性

  • async Taskの場合:
    awaitを使うことで、非同期処理の完了を待機し、例外が発生した場合にも呼び出し元でキャッチ可能です。
    awaitを使わないと、非同期処理の完了を待たずに次の処理が実行されるため、意図しない動作になる可能性があります。
  • 例:
static async Task CallerMethodAsync()
{
    await ProcessWithDelayAsync();
    // 非同期処理の完了後に実行されるコード
}

5. まとめ

  • 非同期メソッドの定義:
    asyncキーワードとTaskを返すことで、非同期処理を記述できる。
    例: static async Task ProcessWithDelayAsync()
  • awaitの役割:
    非同期メソッドの完了を待機し、他の処理をブロックせずに並行処理を可能にする。
  • 戻り値の違い:
    • async Task:呼び出し元でawaitが使え、例外処理も容易。
    • async void:主にイベントハンドラーで利用され、呼び出し元で待機ができない。
  • 呼び出し方法:
    • 非同期処理の場合は、基本的にawaitを使って処理の完了を待つ。
    • 必要に応じて、同期的に呼び出す方法もあるが、デッドロックに注意。

このガイドを参考に、C#で効果的な非同期プログラミングを実現してください。

C#,非同期

Posted by hidepon