Cysharp/UniTask

Cancellation on DOTween works, but I don't catch an OperationCanceledException, nor do I get the correct value of canceled when I suppress it.

SherryOever18 opened this issue · 2 comments

Unity : 2022.3.13f1
UniTask : 2.5.3
DOTween : 1.2.765

Here is the code :

using DG.Tweening;
using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;
using UnityEngine.UI;
using System;

public class NewBehaviourScript : MonoBehaviour
{

    [SerializeField]
    RectTransform rectTransform;

    [SerializeField]
    Button button;

    CancellationTokenSource moveCancellation;

    void Start()
    {
        moveCancellation = new CancellationTokenSource();
        button.onClick.AddListener(() => moveCancellation.Cancel());
        Foo(moveCancellation.Token).Forget();
    }

    async UniTaskVoid Foo(CancellationToken cancellationToken)
    {
        try
        {
            await rectTransform.DOAnchorPos(1000f * Vector2.right, 10f).WithCancellation(cancellationToken);
        }
        catch (OperationCanceledException ex)
        {
            Debug.LogException(ex);
        }
    }

    void OnDestroy()
    {
        moveCancellation.Dispose();
    }
}
using DG.Tweening;
using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;
using UnityEngine.UI;

public class NewBehaviourScript : MonoBehaviour
{

    [SerializeField]
    RectTransform rectTransform;

    [SerializeField]
    Button button;

    CancellationTokenSource moveCancellation;

    void Start()
    {
        moveCancellation = new CancellationTokenSource();
        button.onClick.AddListener(() => moveCancellation.Cancel());
        Foo(moveCancellation.Token).Forget();
    }

    async UniTaskVoid Foo(CancellationToken cancellationToken)
    {
        bool canceled = await rectTransform.DOAnchorPos(1000f * Vector2.right, 10f).WithCancellation(cancellationToken).SuppressCancellationThrow();
        Debug.Log(canceled);
    }

    void OnDestroy()
    {
        moveCancellation.Dispose();
    }
}

I can confirm these tests fail. While the tasks are indeed cancelled, they neither throw a Cancellation exception nor return the proper bool value when the exception is suppressed.

const float TWEEN_TIME      = 2f;  
const int   DEFAULT_WAIT_MS = 500;  
  
bool _isCanceled;  
  
[UnityTest]  
public IEnumerator DoTweenTaskCanceledReturnValueTest() => UniTask.ToCoroutine(async () => {  
    var cts = new CancellationTokenSource();  
    DoTweenTask(cts.Token).Forget();  
    await UniTask.Delay(DEFAULT_WAIT_MS);  
    cts.Cancel();  
    await UniTask.Delay(DEFAULT_WAIT_MS);  
    Assert.That(_isCanceled, Is.True);  
});  
  
[UnityTest]  
public IEnumerator DoTweenTaskTimeoutReturnValueTest() => UniTask.ToCoroutine(async () => {  
    using var timeoutController = new TimeoutController();  
    DoTweenTask(timeoutController.Timeout(DEFAULT_WAIT_MS)).Forget();  
    await UniTask.Delay(DEFAULT_WAIT_MS + 100);  
    Assert.That(_isCancelled, Is.True);  
});  
  
async UniTaskVoid DoTweenTask(CancellationToken token) {  
    _isCanceled = false;  
    var time  = Time.time;  
    var value = 0;  
    _isCanceled = await DOTween.To(() => value, x => value = x, 100, TWEEN_TIME)  
                               .WithCancellation(token)  
                               .SuppressCancellationThrow();  
    time = Time.time - time;  
    Debug.Log(time < TWEEN_TIME  
                  ? $"{nameof(DoTweenTask)} is cancelled after {time} seconds"  
                  : $"{nameof(DoTweenTask)} is completed after {time} seconds");  
}

@SherryOever18
The behavior when a DOTween is canceled can be optionally adjusted.
By default, no exception is used, but the following will throw an OperationCancellationException while killing the tween.

await rectTransform.DOAnchorPos(1000f * Vector2.right, 10f)
    .ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, cancellationToken);