はなちるのマイノート

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

【UniTask】UniTaskCompletionSourceを使って好きなタイミングで結果を確定させるUniTaskを生成する(ついでにUniTask.Voidの紹介)

はじめに

今回はUniTaskCompletionSourceについて紹介したいと思います。

github.com

UniTaskCompletionSourceを使うことで好きなタイミングで結果を確定させるUniTaskを生成することができます。

早速見ていきましょう。

UniTaskCompletionSource

f:id:hanaaaaaachiru:20210710215455p:plain
UniTaskCompletionSourceの使い方

new UniTaskCompletionSource()からTrySetResult(), TrySetCanceled(), TrySetException()が呼ばれるまで待つUniTaskUniTaskCompletionSource.Taskから生成することができます。

public UniTask Task
{
    [DebuggerHidden]
    get
    {
        return new UniTask(this, 0);
    }


実際に利用しているコードを見た方がわかりやすいでしょう。

public class UniTaskTest
{
    [UnityTest]
    public IEnumerator UniTaskCompletionSourceTest()
    {
        // 返り値を持たないUniTaskを生成するUniTaskCompletionSourceを生成
        var uts = new UniTaskCompletionSource();
        var isSuccess = false;

        // 非同期でUniTaskをawaitする
        UniTask.Void(async () =>
        {
            await uts.Task;
            isSuccess = true;
        });
        
        // UniTaskを完了させて、awaitを抜けさせる
        uts.TrySetResult();

        yield return null;
        
        Assert.AreEqual(isSuccess, true);
    }

    [UnityTest]
    public IEnumerator UniTaskCompletionSourceTest2()
    {
        // 返り値を持つUniTaskを生成するUniTaskCompletionSourceを生成
        var uts = new UniTaskCompletionSource<int>();

        // 非同期でUniTaskをawaitする
        UniTask.Void(async () =>
        {
            var result = await uts.Task;
            Assert.AreEqual(result, 10);
        });

        // UniTaskを完了させて、awaitを抜けさせる
        uts.TrySetResult(10);

        yield return null;
        yield return null;
    }
    
    [UnityTest]
    public IEnumerator UniTaskCompletionSourceTest3()
    {
        // 返り値を持たないUniTaskを生成するUniTaskCompletionSourceを生成
        var uts = new UniTaskCompletionSource();
        var isSuccess = false;
        
        // 非同期でUniTaskをawaitする
        UniTask.Void(async () =>
        {
            try
            {
                await uts.Task;
                Assert.Fail();
            }
            catch (OperationCanceledException)
            {
                isSuccess = true;
            }
        });
        
        // UniTaskを完了させて、awaitを抜けさせる
        uts.TrySetCanceled();

        yield return null;
        Assert.AreEqual(isSuccess, true);
    }
    
    [UnityTest]
    public IEnumerator UniTaskCompletionSourceTest4()
    {
        // 返り値を持たないUniTaskを生成するUniTaskCompletionSourceを生成
        var uts = new UniTaskCompletionSource();
        var isSuccess = false;

        // 非同期でUniTaskをawaitする
        UniTask.Void(async () =>
        {
            try
            {
                await uts.Task;
                Assert.Fail();
            }
            catch (NotImplementedException)
            {
                isSuccess = true;
            }
        });

        // UniTaskを完了させて、awaitを抜けさせる
        uts.TrySetException(new NotImplementedException());

        yield return null;
        Assert.AreEqual(isSuccess, true);
    }
}

UniTaskの結果を確定させるメソッドは以下の3つのメソッドになります。

名前 意味
TrySetResult(), TrySetResult(T) 結果をセットする
TrySetException(Exception) ステータスを失敗状態にする
TrySetCanceled(CancellationToken) ステータスをキャンセル状態にする

UniTaskCompletionSource Class | UniTask
UniTaskCompletionSource<T> Class | UniTask


またUniTaskCompletionSourceは一度結果を確定した後、再度上記のメソッドを読んで状態・結果を変化させることはできないので注意してください。

if ((UniTaskStatus)intStatus != UniTaskStatus.Pending) return false;

UniTask.Void

そういえば今まで何気なくUniTask.Voidというファクトリメソッドを使ってましたが、そういえば一度も紹介していなかったので紹介しておきます。

// <summary>
/// helper of fire and forget void action.
/// </summary>
public static void Void(Func<UniTaskVoid> asyncAction)
{
    asyncAction().Forget();
}

UniTask.Voidの引数に取ったFuncは投げっぱなし(Fire and Forget)として実行されます。

つまりは以下のコードと同じ意味ですね。

// UniTask.Voidを使ったもの
UniTask.Void(async () =>
{
    await UniTask.Yield();
    Debug.Log("hello");
});

↓

// 愚直に書いたもの
Hoge().Forget();

private async UniTaskVoid Hoge()
{
    await UniTask.Yield();
    Debug.Log("hello");
}

わざわざメソッドを定義するのが面倒くさいときは便利なので、使ってみると良いかもしれません。