本文へスキップ
BecomeCoder

C#実践コース · 第11章 モダンC#実務 · レッスン60

ValueTask と IAsyncDisposable

ブラウザで完結

導入

非同期の戻り値は Task が基本ですが、「多くの場合すぐ答えが用意できる」処理では、毎回 Task を割り当てるのが無駄になります。ValueTask はその無駄を抑える軽量版。また、後片付け自体が非同期な資源には IAsyncDisposableawait using を使います。

図解

flowchart LR
    A["ValueTask<T>"] -->|即答できる| S["Task を割り当てず値を返す(軽い)"]
    A -->|待ちが要る| T["内部で Task を使う"]
    style S fill:#e8f5e9

サンプル

// すぐ値を用意できるので、Task を割り当てずに返す
Console.WriteLine(await GetCachedAsync());   // 42

ValueTask<int> GetCachedAsync() => new ValueTask<int>(42);

// 後片付けが非同期な資源は IAsyncDisposable。await using で確実に閉じる
await using (var conn = new AsyncConnection())
{
    await conn.SendAsync("hello");
}

class AsyncConnection : IAsyncDisposable
{
    public async Task SendAsync(string msg)
    {
        await Task.Yield();
        Console.WriteLine($"送信: {msg}");
    }
    public async ValueTask DisposeAsync()
    {
        await Task.Yield();
        Console.WriteLine("接続を閉じた");
    }
}

出力:

42
送信: hello
接続を閉じた
  • ValueTask<T> は「即答できることが多い非同期」でアロケーションを抑える(結果は一度だけ await する)
  • IAsyncDisposableDisposeAsync に非同期の後片付けを書く
  • await usingスコープを抜けるとき DisposeAsync を確実に呼ぶ

やってみよう

GetCachedAsync の中を await Task.Delay(1) してから値を返す形に変えても、呼び出し側 await GetCachedAsync() はそのまま動きます。ValueTaskが「即答も待ちも」表せることを確かめましょう。

演習

// TODO: ValueTask<int> を返し、100 を即座に返す GetScore を定義してください
___

Console.WriteLine(await GetScore());   // 100
  • 期待される出力: 100
ヒント1を見る

ValueTask<int> GetScore() => new ValueTask<int>(100);

ヒント2を見る

待ちが不要なら new ValueTask<int>(値) で即座に返せます

まとめ

  • ValueTask<T> は「即答が多い非同期」の軽量版(結果の await は一度だけ)
  • IAsyncDisposable + await using で非同期の後片付けを確実に行う
  • どちらも高頻度な非同期処理でのアロケーション削減に効く

次回: 生産者と消費者をつなぐ Channel です(読み物)。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。