Canceling an UniTask that was run by UniTask.RunOnThreadPool()

What i’m trying to do

I am trying to make a RunTask class, in the constructor of which an argument of type UniTask is accepted (this argument will be named task). When an instance of the class is created, the argument we passed to the constructor will be run via UniTask.RunOnThreadPool(action: () => task). Also the RunTask class contains the Abort() method, where the logic of canceling the UniTask launched in the constructor should take place.

What’s the problem

I’m trying to cancel a running UniTask via CancellationToken, but in the last 2 days I’ve been getting more and more confused about how canceling a Task in C# even works. As far as I understand, it is necessary to write logic in each async UniTask methods when cancellation is requested, if it is true, it is just inconvenient and I need to add cancellation logic to the task argument in the constructor, and if it is not true, I have tried almost everything to cancel a running UniTask when CancellationTokenSource.Cancel() is called.

Example usage of RunTask

public class RunTaskExample : MonoBehaviour
{
  private RunTask _moveLogicTask;

  private void Start()
  {
    _moveLogicTask = new RunTask(MoveLogic(delaySeconds: 2f));
  }

  private void Update()
  {
    if (Input.GetKeyDown(KeyCode.C)) _moveLogicTask?.Abort(); // if C is pressed abort the task
  }

  private async UniTask MoveLogic(float delaySeconds)
  {
    MoveUp();
    await Delay.Seconds(delaySeconds); // my custom class for better readability
    MoveLeft();
  }
}

UniTask.AttachExternalCancellation

UniTask class instances have one very interesting method - AttachExternalCancellation(CancellationToken token). When I was investigating my problem, I found a user who had a problem in canceling UniTask.WhenAny(), and the AttachExternalCancellation() method worked for him. I tried several times to use this method, but UniTask cancellation didn’t work either, but it’s possible that I just used it wrong, but how to use it correctly I can’t understand.

Some versions of RunTask class (not every version)

// current
public class RunTask
{
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private CancellationToken _ct;

    private UniTask _task;

    public RunTask(UniTask task)
    {
        _ct = _cts.Token;

        _task = UniTask.RunOnThreadPool(() => task.AttachExternalCancellation(_ct), cancellationToken: _ct);
    }

    public void Abort()
    {
        _cts?.Cancel();
    }
}

// previous complex
public class RunTask
{
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    private readonly UniTask _task;

    public RunTask(UniTask task)
    {
        _task = UniTask.RunOnThreadPool(async () =>
        {
            try
            {
                await task;

                while (_task.Status == UniTaskStatus.Pending)
                {
                    _cts.Token.ThrowIfCancellationRequested();
                    await Delay.Frame;
                }
            }
            catch (OperationCanceledException) when (_cts.IsCancellationRequested)
            {
                Debug.Log("Task canceled");
                throw;
            }
        }, true, _cts.Token);
    }

    public void Cancel()
    {
        _cts.Cancel();
    }
}

// previous very simple
public class RunTask
{
    private CancellationTokenSource _cts;
    private UniTask _task;

    public RunTask(UniTask task)
    {
        _cts = new CancellationTokenSource();

        _task = UniTask.RunOnThreadPool(async () => await task, true, _cts.Token);
    }

    public void Cancel()
    {
        _cts.Cancel();
    }
}
1 Like