5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ApplibotAdvent Calendar 2022

Day 7

DOTween + UniTask環境でのTweenCancelBehaviourについて

Last updated at Posted at 2022-12-06

はじめに

こちらは「Applibot Advent Calendar 2022」7日目の記事になります。

DOTween + UniTask環境で、ToUniTask()する際にCancellationToken以外にも渡すべきものがあるという話を聞き、どういったものなのか、どういった挙動になるのか気になったため記事にしてみました!

TweenCancelBehaviourとは?

DOTween + UniTask 環境で ToUniTask()の引数に入れるもので、キャンセルされたときの挙動について指定します。

await transform.DOLocalMoveX(10, 1).ToUniTask(TweenCancelBehaviour.Kill, cancellationToken: ct);

デフォルトでTweenCancelBehaviour.Killが入るので指定しなくても動くことは動くのですが、バグに繋がりそうな挙動なので実際にコードと一緒に見ていこうと思います!
今回はシンプルなKill,KillAndCancelAwait,Complete, CompleteAndCancelAwaitの比較を行っていきます!

実行するコード

public class CancellationTokenTest : MonoBehaviour
{
    private CancellationTokenSource _cts;

    async void Start()
    {
        _cts = new CancellationTokenSource();
        await UniTask.Delay(1000, cancellationToken: this.GetCancellationTokenOnDestroy());
        // ここで実行するコードを変えていく
        NanikaAsync(_cts.Token).Forget(e => { print(e.ToString()); });
        await UniTask.Delay(500, cancellationToken: this.GetCancellationTokenOnDestroy());
        _cts.Cancel();
    }
}

今回上記のコードを実行します。見ての通り、Delayで1秒後にNanikaAsyncを実行し、さらに0.5秒後にキャンセルする、といった処理になっています。
NanikaAsyncにあたるコードをこれから変更していき、どう違うのか見ていきます。

Kill(デフォルト)の場合

private async UniTask KillAsync(CancellationToken ct)
{
    await transform.DOLocalMoveX(10, 1).ToUniTask(cancellationToken: ct);
    print(ct.IsCancellationRequested);
    await transform.DOLocalMoveX(-10, 1).ToUniTask(cancellationToken: ct);
    print(ct.IsCancellationRequested);
}

KillAsync.gif
この場合、gifのようになり、1つ目のawait中にキャンセルされたのにも関わらず、例外を出さずに関数2行目print(ct.IsCancellationRequested);が実行されてしまっています。その後、2つ目のAsync関数実行のエントリー時に例外がスローされているため、print(e.ToString());が実行されるという流れ。
ToUniTask()内のコードを見てみるとCreate時にキャンセルのチェックがされて例外を出している感じですね。

// DOTween内ToUniTaskのコード
public static UniTask ToUniTask(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
{
    Error.ThrowArgumentNullException(tween, nameof(tween));
    if (!tween.IsActive()) return UniTask.CompletedTask;
    return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.Kill, out var token), token);
}

public static IUniTaskSource Create(Tween tween, TweenCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, CallbackType callbackType, out short token)
{
    if (cancellationToken.IsCancellationRequested)
    {
        DoCancelBeforeCreate(tween, cancelBehaviour);
        return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
    }
    ...

キャンセルされた時点で例外を出してほしいことがほとんどだと思うので、これだと意図した挙動じゃない場合が多いと思います。

KillAndCancelAwaitの場合

private async UniTask KillAndCancelAwaitAsync(CancellationToken ct)
{
    await transform.DOLocalMoveX(10, 1).ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, cancellationToken: ct);
    print(ct.IsCancellationRequested);
    await transform.DOLocalMoveX(-10, 1).ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, cancellationToken: ct);
    print(ct.IsCancellationRequested);
}

KillAndCancelAwaitAsync.gif
こちらはキャンセル時にちゃんと例外を出してくれているので単純に途中で止める、という挙動の場合はKillAndCancelAwaitが想定通りの挙動になっています。良さそう。

Completeの場合

private async UniTask CompleteAsync(CancellationToken ct)
{
    await transform.DOLocalMoveX(10, 1).ToUniTask(TweenCancelBehaviour.Complete, cancellationToken: ct);
    print(ct.IsCancellationRequested);
    await transform.DOLocalMoveX(-10, 1).ToUniTask(TweenCancelBehaviour.Complete, cancellationToken: ct);
    print(ct.IsCancellationRequested);
}

CompleteAsync.gif
Completeを入れた場合もKillと同じくキャンセルされた後に例外を出さず、2行目のコードprint(ct.IsCancellationRequested);が実行されていますね。さらに、2つ目のAsync関数に入りCompleteされてから例外を出しています。
仕組みをしっかりとは把握できていませんが中を見てみると、TweenCancelBehaviour.Completeの場合TweenConfiguredSourceOnUpdateでキャンセルフラグが立たないため、OnCompleteCallbackDelegate()originalCompleteActionが呼ばれているのが原因っぽいです。詳しく知りたい方はDOTweenの中を見てみることをお勧めします。

動きの話に戻りますが、これも意図した挙動ではないことの方が多いと思うのであまり使い道はなさそう。

CompleteAndCancelAwaitの場合

private async UniTask CompleteAndCancelAwaitAsync(CancellationToken ct)
{
    await transform.DOLocalMoveX(10, 1)
        .ToUniTask(TweenCancelBehaviour.CompleteAndCancelAwait, cancellationToken: ct);
    print(ct.IsCancellationRequested);
    await transform.DOLocalMoveX(-10, 1)
        .ToUniTask(TweenCancelBehaviour.CompleteAndCancelAwait, cancellationToken: ct);
    print(ct.IsCancellationRequested);
}

CompleteAndCancelAwaitAsync.gif
CompleteAndCancelAwaitの場合はCompleteと違い、ちゃんとすぐに例外を出してくれているのでこちらの方が良さそう。

まとめ

ToUniTask()を使う際はTweenCancelBehaviourも意識しよう!
大体の場合はKillAndCancelAwaitCompleteAndCancelAwaitで問題なさそう!

おしまい!

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?