基礎から学ぶC#入門 90日コース | 実践力を高める - Day 63:非同期処理入門

Web APP 90日で身につけるC#
スポンサーリンク
スポンサーリンク

Day 63 のゴールと全体像

Day 63 では、いよいよ 非同期処理 の世界に足を踏み入れていきます。 キーワードはこの3つです。

  • async
  • await
  • Task

どれも C# で「待ち時間の長い処理」をスマートに扱うための、とても重要な道具です。 最初は少しとっつきにくく感じるかもしれませんが、 イメージさえつかめてしまえば「なるほど、そういうことか」と腑に落ちる瞬間が必ず来ます。

ここでは、

  • 「非同期ってそもそも何?」というところから始めて
  • async / await / Task の役割を
  • 小さな例題コードを通して、ステップバイステップでかみ砕いていきます。

非同期処理って何をしてくれるのか

同期処理と非同期処理のざっくりした違い

まずは、イメージから入っていきます。

  • 同期処理
    • 「A が終わるまで、B は待つ」
    • ひとつひとつ順番に、きっちり並んで処理するイメージです。
  • 非同期処理
    • 「A をやっている間に、B も進めていいよ」
    • 待ち時間のあいだに、ほかのことを進めるイメージです。

たとえば、

  • ネットワークからデータを取ってくる
  • ファイルを読み書きする
  • 時間のかかる計算をする

といった処理は、「待ち時間」が発生しやすいです。 その待ち時間のあいだ、アプリが固まってしまうのではなく、 ほかの処理を進めたり、画面を動かしたりできるようにするのが非同期処理の役割です。

Task ― 「いつか終わる約束」を表す箱

Task とは何か

Task は、

「まだ終わっていないけれど、いつか終わる処理」を表すオブジェクト

です。

  • 「今すぐ結果があるわけではないけれど、終わったら結果を返すよ」
  • 「今は進行中だけど、完了したかどうかを後で確認できるよ」

という“約束の箱”のようなイメージです。

まずは Task を返すメソッドを書いてみる

簡単な例として、「3秒待ってからメッセージを返す」メソッドを作ってみます。

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("処理を開始します…");

        // 非同期メソッドを呼び出し、結果を待つ
        string result = await SlowOperation();

        Console.WriteLine($"結果: {result}");
        Console.WriteLine("処理が完了しました。");
    }

    // 時間のかかる処理を表すメソッド
    static async Task<string> SlowOperation()
    {
        Console.WriteLine("3秒待っています…");

        // 3秒待つ(非同期)
        await Task.Delay(3000);

        Console.WriteLine("待ち時間が終わりました。");

        return "完了しました!";
    }
}
C#

ここで登場しているのが、

  • Task<string>
  • async
  • await

の3つです。 ひとつずつ、じっくり見ていきます。

async ― 「このメソッドは非同期ですよ」という宣言

async の役割

async は、

「このメソッドの中では await を使いますよ」

という宣言です。

static async Task<string> SlowOperation()
{
    // ...
}
C#

このように、

  • 戻り値が Task または Task<T> のメソッド
  • あるいは async void(イベントハンドラなど)

async を付けることで、 そのメソッドの中で await が使えるようになります。

async を付けると何が変わるのか

async を付けたメソッドは、

  • 中で await を使うと
  • その await のところで「一時的に処理を中断」し
  • 後で再開する、という動きができるようになります。

ただし、ここで大事なのは、

「async を付けたからといって、勝手に並列処理になるわけではない」

ということです。

async はあくまで、

  • 「このメソッドは非同期的に動く可能性があります」
  • 「中で await を使います」

という“宣言”であり、 実際の動きは awaitTask が決めていきます。

await ― 「終わるまで、ここでいったん待ちます」

await の役割

await は、

「この Task が終わるまで、いったん待ってから先に進みます」

というキーワードです。

await Task.Delay(3000);
C#

この1行には、

  • 「3秒待つ処理(Task)」を
  • 「終わるまで待ってから、次の行に進む」

という意味が込められています。

await の動き方をイメージで追う

先ほどの SlowOperation をもう一度見てみます。

static async Task<string> SlowOperation()
{
    Console.WriteLine("3秒待っています…");

    await Task.Delay(3000); // ここでいったん“待つ”

    Console.WriteLine("待ち時間が終わりました。");

    return "完了しました!";
}
C#

このメソッドの流れは、

  1. 「3秒待っています…」と表示する
  2. Task.Delay(3000) を呼び出す
  3. await によって、「3秒経つまで、ここで中断」する
  4. 3秒経ったら、メソッドの続きが再開される
  5. 「待ち時間が終わりました。」と表示する
  6. "完了しました!" を返す

という形になります。

await の重要ポイント

  • await は「Task が終わるまで待つ」ためのキーワードです。
  • await を使うには、そのメソッドに async が付いている必要があります。
  • await した行の「後ろの処理」は、Task が完了したあとに再開されます。

Task ― 「結果を持っているかもしれない箱」

Task と Task<T> の違い

Task には大きく分けて2種類あります。

  • Task
    • 「戻り値のない非同期処理」を表す
  • Task<T>
    • 「T 型の結果を返す非同期処理」を表す

先ほどの例では、

static async Task<string> SlowOperation()
C#

と書いていました。 これは「string を返す非同期処理」を表しています。

Task を返すメソッドの基本形

戻り値なしの非同期メソッド

static async Task DoSomethingAsync()
{
    Console.WriteLine("何かをしています…");
    await Task.Delay(1000);
    Console.WriteLine("終わりました。");
}
C#

戻り値ありの非同期メソッド

static async Task<int> CalculateAsync()
{
    Console.WriteLine("計算中…");
    await Task.Delay(1000);
    Console.WriteLine("計算が終わりました。");

    return 42; // 結果を返す
}
C#

呼び出し側での使い方

static async Task Main()
{
    // 戻り値なしの非同期メソッド
    await DoSomethingAsync();

    // 戻り値ありの非同期メソッド
    int result = await CalculateAsync();
    Console.WriteLine($"結果は {result} です。");
}
C#

ここでのポイントは、

  • 非同期メソッドを呼び出すときも await を使う
  • await した結果を、普通の変数に代入できる

というところです。

非同期処理の流れを、もう少し丁寧に追ってみる

例題:2つの時間のかかる処理を順番に待つ

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("処理を開始します…");

        string result1 = await SlowOperation("処理1", 2000);
        Console.WriteLine($"処理1の結果: {result1}");

        string result2 = await SlowOperation("処理2", 3000);
        Console.WriteLine($"処理2の結果: {result2}");

        Console.WriteLine("すべての処理が完了しました。");
    }

    static async Task<string> SlowOperation(string name, int milliseconds)
    {
        Console.WriteLine($"{name} を開始します({milliseconds}ミリ秒)…");

        await Task.Delay(milliseconds); // 指定時間だけ待つ

        Console.WriteLine($"{name} が完了しました。");

        return $"{name}の完了メッセージ";
    }
}
C#

このプログラムの流れは、

  1. 「処理を開始します…」と表示
  2. SlowOperation("処理1", 2000) を呼び出し、2秒待つ
  3. 「処理1の結果: …」を表示
  4. SlowOperation("処理2", 3000) を呼び出し、3秒待つ
  5. 「処理2の結果: …」を表示
  6. 「すべての処理が完了しました。」を表示

という形になります。

ここで押さえておきたいポイント

  • SlowOperation は「時間のかかる処理」を表す非同期メソッドです。
  • await SlowOperation(...) と書くことで、 「その処理が終わるまで待ってから次に進む」という動きになります。
  • Task.Delay は「待ち時間」を表す便利なメソッドで、 非同期処理の練習にぴったりです。

非同期処理でよくあるつまずきポイント

1. async を付け忘れて await を書いてしまう

static Task<string> SlowOperation()
{
    await Task.Delay(1000); // コンパイルエラーになる
    return "OK";
}
C#

この場合、

  • awaitasync メソッドの中でしか使えません」

というコンパイルエラーになります。

対処:

static async Task<string> SlowOperation()
{
    await Task.Delay(1000);
    return "OK";
}
C#

と、async を付けてあげる必要があります。

2. 非同期メソッドを呼び出しているのに await していない

static async Task Main()
{
    SlowOperation(); // await していない

    Console.WriteLine("終わりました。");
}
C#

この場合、

  • SlowOperation が「いつ終わるか」を待たずに
  • 「終わりました。」が先に表示されてしまう可能性があります。

対処:

static async Task Main()
{
    await SlowOperation(); // 終わるまで待つ

    Console.WriteLine("終わりました。");
}
C#

と、await を付けてあげることで、 「SlowOperation が終わってから次に進む」ようになります。

非同期処理のミニテンプレート

最後に、async / await / Task を使うときの基本形を、 テンプレートとしてまとめておきます。

戻り値なしの非同期メソッド

static async Task DoSomethingAsync()
{
    // 何かの前処理
    Console.WriteLine("前処理中…");

    // 時間のかかる処理を待つ
    await Task.Delay(1000);

    // 後処理
    Console.WriteLine("後処理が終わりました。");
}
C#

戻り値ありの非同期メソッド

static async Task<int> CalculateAsync()
{
    Console.WriteLine("計算中…");

    await Task.Delay(1000); // 計算に時間がかかるイメージ

    Console.WriteLine("計算が終わりました。");

    return 123; // 計算結果
}
C#

呼び出し側(Main)の基本形

static async Task Main()
{
    Console.WriteLine("非同期処理のテストを開始します。");

    await DoSomethingAsync();

    int result = await CalculateAsync();
    Console.WriteLine($"計算結果は {result} です。");

    Console.WriteLine("すべての処理が完了しました。");
}
C#

Day 63 のまとめ

Day 63 では、

  • 非同期処理のざっくりしたイメージ(待ち時間のあいだにほかのことを進める)
  • Task(「いつか終わる約束」を表す箱)
  • async(「このメソッドは非同期ですよ」という宣言)
  • await(「この Task が終わるまで待ちます」というキーワード)

を、例題コードとともに見ていきました。

非同期処理は、最初はどうしても抽象的に感じやすいテーマですが、

  • Task.Delay を使って「待ち時間」を体験してみる
  • async / await をセットで使ってみる
  • 戻り値あり・なしの非同期メソッドを書いてみる

といった小さな練習を重ねていくうちに、 「なるほど、こういうときに使うんだな」という感覚が少しずつ育っていきます。

ぜひ、

  • ファイル読み込みを非同期でやってみる
  • Web API 呼び出しを非同期でやってみる(後の章で扱う題材にもつながります)

など、実際の“待ち時間のある処理”に応用しながら、 非同期処理を自分の武器のひとつにしていっていただければうれしいです。

タイトルとURLをコピーしました