はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】AwaitableCompletionSourceを用いて好きなタイミングでAwaitableの結果を確定させる

はじめに

今回はUnity2023.1から登場したAwaitableに関連した、AwaitableCompletionSourceについて紹介をしたいと思います。

www.hanachiru-blog.com

概要

まずは公式ドキュメントに書いてある説明から見てみましょう。

Objects allowing to control completion of an Awaitable object from user code.

// DeepL翻訳
ユーザーコードからAwaitableオブジェクトの完了を制御することができるオブジェクトです。

docs.unity3d.com

Taskで言うところのTaskCompletionSourceUniTaskで言うところのUniTaskCompletionSourceAwaitableバージョンになります。

learn.microsoft.com
www.hanachiru-blog.com

ざっと言うと好きなタイミングでAwaitableもしくはAwaitable<T>の結果 or キャンセル or エラーをセットできる機能でしょうか。

private async Awaitable Start()
{
    AwaitableCompletionSource<int> acs = new AwaitableCompletionSource<int>();
    Awaitable<int> awaitable = acs.Awaitable;

    // Fire and Forget
    _ = SetResult(acs, destroyCancellationToken);
        
    // 1秒後まで待ち、結果が確定
    var result = await awaitable;
        
    // 10
    Debug.Log(result);
}

private static async Awaitable SetResult(AwaitableCompletionSource<int> acs, CancellationToken token)
{
    await Awaitable.WaitForSecondsAsync(1, token);
    acs.SetResult(10);
}

使い方

重要なメソッドは以下の通り。

  • AwaitableCompletionSource.SetResult
  • AwaitableCompletionSource.TrySetResult
  • AwaitableCompletionSource.SetCanceled
  • AwaitableCompletionSource.TrySetCanceled
  • AwaitableCompletionSource.SetException
  • AwaitableCompletionSource.TrySetException

awaitした際の返り値がある場合はAwaitableCompletionSource<T>を利用します。

private async Awaitable Start()
{
    // ----------------
    // 返り値なしバージョン
    // ----------------
    AwaitableCompletionSource acs = new AwaitableCompletionSource();
    Awaitable awaitable = acs.Awaitable;

    // Fire and Forget
    _ = SetResult(acs, destroyCancellationToken);

    // 1秒後まで待ち、結果が確定
    await awaitable;

    // ----------------
    // 返り値ありバージョン
    // ----------------
    AwaitableCompletionSource<int> acs2 = new AwaitableCompletionSource<int>();
    Awaitable<int> awaitable2 = acs2.Awaitable;

    _ = SetResult2(acs2, destroyCancellationToken);

    // 1秒後まで待つ
    var result = await awaitable2;

    // 10
    Debug.Log(result);

    // ----------------
    // キャンセルさせる
    // ----------------
    AwaitableCompletionSource<int> acs3 = new AwaitableCompletionSource<int>();
    Awaitable<int> awaitable3 = acs3.Awaitable;

    _ = SetCanceled(acs3, destroyCancellationToken);

    // 1秒後まで待つ
    try
    {
        var result3 = await awaitable3;
    }
    catch (OperationCanceledException)
    {
        Debug.Log("ここを通る");
    }

    // ----------------
    // エラーを発生させる
    // ----------------
    AwaitableCompletionSource<int> acs4 = new AwaitableCompletionSource<int>();
    Awaitable<int> awaitable4 = acs4.Awaitable;

    _ = SetException(acs4, destroyCancellationToken);

    try
    {
        var result4 = await awaitable4;
    }
    catch (NotImplementedException)
    {
        Debug.Log("ここを通る");
    }
}

private static async Awaitable SetResult(AwaitableCompletionSource acs, CancellationToken token)
{
    // 1秒後に結果を確定する
    await Awaitable.WaitForSecondsAsync(1, token);
    acs.SetResult();
}

private static async Awaitable SetResult2(AwaitableCompletionSource<int> acs, CancellationToken token)
{
    // 1秒後に結果を確定する
    await Awaitable.WaitForSecondsAsync(1, token);
    acs.SetResult(10);
}

private static async Awaitable SetCanceled(AwaitableCompletionSource<int> acs, CancellationToken token)
{
    // 1秒後にキャンセルする
    await Awaitable.WaitForSecondsAsync(1, token);
    acs.SetCanceled();
}

private static async Awaitable SetException(AwaitableCompletionSource<int> acs, CancellationToken token)
{
    // 1秒後にエラーをセットする
    await Awaitable.WaitForSecondsAsync(1, token);
    acs.SetException(new NotImplementedException());
}