【UniTask】Cancellation of UniTasks

UniTask
Tool versions
  • Unity: 2022.3.35f1
  • UniTask: 2.5.4

Fundamental of Cancellation of UniTasks

An UniTask is a kind of asynchronous processing, which takes time to complete. When executing asynchronous processing, you often want to stop that process before it finishes in some cases (e.g. you tried to retrieve data from a server, but it seems to take unacceptable time to be completed, therefore you want to cancel the process and notify it to the user).

In this article, we are going to learn how we can cancel the process of an UniTask running.

CancellationToken Object

An UniTask can be canceled using a CancellationToken object. As an example, create the following code, attach it to an object, and run the code.

SampleManager.cs
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class SampleManager : MonoBehaviour
{
	private CancellationTokenSource cancellationTokenSource = new();

	private void Start()
	{
		CancellationToken cancellationToken = cancellationTokenSource.Token;
		_ = StartTimerAsync("Timer A", 3, cancellationToken);
	}

	private void Update()
	{
		if (Input.GetKeyDown(KeyCode.Space))
		{
			Debug.Log("Spacekey pushed!");
			cancellationTokenSource.Cancel();
		}
	}

	private void OnDestroy()
	{
		cancellationTokenSource?.Cancel();
		cancellationTokenSource?.Dispose();
	}

	private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
	{
		Debug.Log($"{name} started...");
		while (seconds > 0)
		{
			Debug.Log($"{name}: {seconds} sec.");
			seconds--;
			await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
		}
		Debug.Log($"{name} finished!");
	}
}
C#

If you don’t anything after starting the execution, the timer will finish in the specified seconds.

If you press the spacekey within the specified seconds, the timer will stop.

We are going to see how the UniTask was canceled.

Firstly, the StartTimerAsync method requires a CancellationToken object as its parameter. The CancellationToken object is an object that causes cancellation of UniTasks, and we can create it in many ways. In the example, we create an instance of the CancellationTokenSource class and retrieve a CancellationToken object from it.

Retrieve a CancellationToken object from a CancellationTokenSource instance
private CancellationTokenSource cancellationTokenSource = new();

private void Start()
{
	CancellationToken cancellationToken = cancellationTokenSource.Token;
	_ = StartTimerAsync("Timer A", 3, cancellationToken);
}
C#

The CancellationToken object given to the StartTimerAsync method as an argument is then given to the UniTask.Delay method. The UniTask.Delay method throws a OperationCanceledException exception when the given CancellationToken object is canceled, which stops the process.

Give a CancellationToken object to the UniTask.Delay method
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
C#

The CancellationToken object retrieved from the CancellationTokenSource instance is canceled when the Cancel method of the CancellationTokenSource instance is called. In the example, the instance of the CancellationTokenSource class is canceled when the spacekey is pressed, which leads to the cancellation of the CancellationToken object.

Cancel the CancellationTokenSource instance when the spacekey is pressed
private void Update()
{
	if (Input.GetKeyDown(KeyCode.Space))
	{
		Debug.Log("Spacekey pushed!");
		cancellationTokenSource.Cancel();
	}
}
C#

In summary, the process of the StartTimerAsync method is canceled by following the steps below:

  • The spacekey is pressed
  • The Cancel method of the CancellationTokenSource instance is called
  • The CancellationToken object that was given to the StartTimerAsync method is canceled
  • A OperationCanceledException exception is thrown by the UniTask.Delay method
  • The process of the StartTimerAsync method is canceled by the thrown exception

Additionally, as the CancellationTokenSource class implements the IDisposable interface, we have to call the Dispose method. Furthermore, the Cancel method must be called before the Dispose method is called. The reason of this is that we should ensure that the process of the UniTask which received the CancellationToken object retrieved from the CancellationTokenSource instance finishes at the end of the program. We are going to learn about this in detail later.

Ensure that the CancellationTokenSource is canceled and disposed at the end of the program
private void OnDestroy()
{
	cancellationTokenSource?.Cancel();
	cancellationTokenSource?.Dispose();
}
C#

Define Behavior of UniTasks When Canceled

Cancellation of UniTasks is caused by a thrown OperationCanceledException. Therefore, using try-catch statement, we can define behavior of a canceled UniTask.

Define a process that is executed when an UniTask is canceled
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
	try
	{
		Debug.Log($"{name} started...");
		while (seconds > 0)
		{
			Debug.Log($"{name}: {seconds} sec.");
			seconds--;
			await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
		}
		Debug.Log($"{name} finished!");
	}
	catch (OperationCanceledException)
	{
		Debug.Log($"{name} canceled!");
		throw;
	}
}
C#

Also, using try-catch-finally, we can define a process that is absolutely executed regardless of whether or not an UniTask is canceled.

Define a process that is executed regardless of cancellation
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
	try
	{
		Debug.Log($"{name} started...");
		while (seconds > 0)
		{
			Debug.Log($"{name}: {seconds} sec.");
			seconds--;
			await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
		}
		Debug.Log($"{name} finished!");
	}
	catch (OperationCanceledException)
	{
		Debug.Log($"{name} canceled!");
		throw;
	}
	finally
	{
		Debug.Log("From finally...");
	}
}
C#
When not canceled
When canceled
Should a thrown OperationCancellationException Be Thrown Again?

The OperationCanceledException, which is thrown when an UniTask is canceled, is a kind of exception. Hence, after the exception is handled by catch statement, the following behavior veries depending on whether the exception is thrown by throw; again.

For example, the following two codes show different results when their UniTasks are canceled.

Throw the exception again
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
	try
	{
		Debug.Log($"{name} started...");
		while (seconds > 0)
		{
			Debug.Log($"{name}: {seconds} sec.");
			seconds--;
			await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
		}
		Debug.Log($"{name} finished!");
	}
	catch (OperationCanceledException)
	{
		Debug.Log($"{name} canceled!");
		throw;
	}
	Debug.Log("End of method");
}
C#
Don’t throw the exception again
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
	try
	{
		Debug.Log($"{name} started...");
		while (seconds > 0)
		{
			Debug.Log($"{name}: {seconds} sec.");
			seconds--;
			await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
		}
		Debug.Log($"{name} finished!");
	}
	catch (OperationCanceledException)
	{
		Debug.Log($"{name} canceled!");
	}
	Debug.Log("End of method");
}
C#

When the UniTask is canceled, the first code doesn’t show the message “End of method” while the second code does.

That’s because in the first code, the processes after the try-catch statement are canceled due to the exception that is thrown again in the catch statement, on the other hand, in the second code, the processes after the try-catch statement are not affected by the cancellation because the handeled exception is squashed in the catch statement.

When you define a process that is executed only when an UniTask is canceled by catching the thrown OperationCanceledException, you should consider whether you want to ignore that cancellation or whether you want to propagate the cancellation in other places in order to define proper behavior of the code depending on your requirement.

Define Cancellation Judgement By Yourself

The cancellation of UniTasks in the previous section examples is caused by a CancellationToken being canceled and a OperationCanceledException being thrown by the UniTask.Delay method to which the canceled CancellationToken was given.

You can basically leave such processes to already-prepared methods like the UniTask.Delay method, but you can also check whether a CancellationToken was canceled and throw an exception if necessary by yourself.

Check if a CancellationToken was canceled and throw a OperationCanceledException if necessary
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
	Debug.Log($"{name} started...");
	while (seconds > 0)
	{
		Debug.Log($"{name}: {seconds} sec.");
		seconds--;
		await UniTask.Delay(TimeSpan.FromSeconds(1.0f));
		if (cancellationToken.IsCancellationRequested)
		{
			throw new OperationCanceledException();
		}
	}
	Debug.Log($"{name} finished!");
}
C#

In the above example, whether the CancellationToken had been canceled is checked by the cancellationToken.IsCancellationRequested method, and an exception is thrown if it had been canceled.

This code can also be written as follows:

Determine if a CancellationToken was canceled using the ThrowIfCancellationRequested method
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
	Debug.Log($"{name} started...");
	while (seconds > 0)
	{
		Debug.Log($"{name}: {seconds} sec.");
		seconds--;
		await UniTask.Delay(TimeSpan.FromSeconds(1.0f));
		cancellationToken.ThrowIfCancellationRequested();
	}
	Debug.Log($"{name} finished!");
}
C#

The ThrowIfCancellationRequested method checks the status of cancellation of a CancellationToken and throws a OperationCanceledException if it had been canceled.

Note that the behavior of this example is slightly different from that of codes where a CancellationToken is given to the UniTask.Delay method directly. If a CancellationToken is given to the UniTask.Delay directly, the following processes are canceled immediately when the CancellationToken is canceled. In the above example, however, when the CancellationToken is canceled, an exception is thrown after the process of the UniTask.Delay method finishes, and then the following processes are canceled (you can see this difference clearly by specifying long delay time for the UniTask.Delay method).

How to Create a CancellationToken

In the previous section examples, we retrieved a CancellationToken object that is required to cancel an UniTask from an instance of the CancellationTokenSource class.

In practice, we can obtain a CancellationToken object not only from CancellationTokenSource but also in several other ways. We are going to learn them in this section.

Retrieve a CancellationToken from a CancellationTokenSource

The most standard way to retrieve a CancellationToken is to retrieve it from an instance of the CancellationTokenSource class as we learned already.

Both CancellationToken and CancellationTokenSource are actually provided as C# standard functions, which are used to cancel asynchronous processing of the C# standard Task class. They are defined in the System.Threading namespace.

Retrieve a CancellationToken from a CancellationTokenSource
using System.Threading;

// Create a CancellationTokenSource instance
CancellationTokenSource cancellationTokenSource = new();
// Retrieve a CancellationToken
CancellationToken cancellationToken = cancellationTokenSource.Token;
// Cancel the CancellationTokenSource when it is required
cancellationTokenSource.Cancel();
// MUST dispose of the CancellationSource after using it
cancellationTokenSource.Dispose();
C#

Convert an UniTask into a CancellationToken

We can obtain a CancellationToken by converting an UniTask. This CancellationToken is canceled when the source UniTask finishes.

Obtain a CancellationToken by converting an UniTask
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class SampleManager : MonoBehaviour
{
	private void Start()
	{
		CancellationToken tokenFromUniTask = StartTimerAsync("Timer A", 2, default).ToCancellationToken();
		_ = StartTimerAsync("Timer B", 5, tokenFromUniTask);
	}

	private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
	{
		try
		{
			Debug.Log($"{name} started...");
			while (seconds > 0)
			{
				Debug.Log($"{name}: {seconds} sec.");
				seconds--;
				await UniTask.Delay(TimeSpan.FromSeconds(1.0f));
				await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
			}
			Debug.Log($"{name} finished!");
		}
		catch (OperationCanceledException)
		{
			Debug.Log($"{name} canceled!");
		}
	}
}
C#

In this example, the StartTimerAsync method are called twice, specifying 2 seconds (for Timer A) and 5 seconds (for Timer B), respectively. The UniTask of Timer A is converted into a CancellationToken object by the ToUniTask method, and it is given to the UniTask of Timer B.

When the program starts, as the specified number of seconds for Timer A is shorter than that for Timer B, the UniTask of Timer A finishes faster than that of Timer B. When the UniTask of Timer A finishes, the converted CancellationToken is canceled, which leads the UniTask of Timer B to which it is given to be canceled.

In this way, converting an UniTask by the ToCancellationToken method, we can create a CancellationToken that is canceled when the source UniTask finishes.

Generate a CancellationToken from a GameObject

We can obtain a CancellationToken from a GameObject by the GetCancellationTokenOnDestroy method, where the CancellationToken is canceled when the source GameObject is destroyed.

Obtain a CancellationToken from a GameObject
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class SampleManager : MonoBehaviour
{
	private async UniTaskVoid Start()
	{
		GameObject sourceObject = new();
		CancellationToken tokenFromObject = sourceObject.GetCancellationTokenOnDestroy();
		_ = StartTimerAsync("Timer A", 2, tokenFromObject);
		await UniTask.Delay(TimeSpan.FromSeconds(1));
		Destroy(sourceObject);
	}

	private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
	{
		try
		{
			Debug.Log($"{name} started...");
			while (seconds > 0)
			{
				Debug.Log($"{name}: {seconds} sec.");
				seconds--;
				await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
			}
			Debug.Log($"{name} finished!");
		}
		catch (OperationCanceledException)
		{
			Debug.Log($"{name} canceled!");
		}
	}
}
C#

In this example, we create a GameObject at first, and then we obtain a CancellationToken object from it by the GetCancellationTokenOnDestroy method. The obtained object is given to the StartTimerAsync method. After the method is called, the source GameObject is destroyed in 1 second.

You employ this way, for example, if you create a CancellationToken that is given to an UniTask that will never finish. As we learn later, it must be ensured that all UniTasks are absolutely canceled when it becomes unnecessary because otherwise resource leak might occur.

One common scenario where some UniTasks become unnecessary is when a scene transitions from the current one to a new one. UniTasks that are needed only for the current scene are unnecessary for the new scene, therefore they must be canceled. When a scene transition occurs, as all objects in the old scene are destroyed, UniTasks whose CancellationToken is obtained by the GetCancellationTokenOnDestroy method are automatically canceled.

Obtain a CancellationToken that is canceled automatically when a scene transition occurs
CancellationToken tokenFromObject = this.GetCancellationTokenOnDestroy();
// An UniTask that never finishes
_ = NeverFinishAsync(tokenFromObject);
// A scene transitioni occurs heare, then the object will be destroyed and the CancellationToken will be canceled.
SceneManager.LoadScene("Next Scene");
C#

Although we can write similar code with a CancellationTokenSource and the OnDestroy method, the code will be redundant.

CancellationTokenSource version
private CancellationTokenSource cancellationTokenSource = new();

private void Start()
{
	CancellationToken cancellationToken = cancellationTokenSource.Token;
	_ = NeverFinishAsync(cancellationToken);
}

private void OnDestroy()
{
	cancellationTokenSource?.Cancel();
	cancellationTokenSource?.Dispose();
}
C#

Assure an UniTask Finishes

We learned that we can cancel the process of an UniTask by passing a CancellationToken to that UniTask. Now, you should consider a CancellationToken as an essential parameter rather than an optional parameter given to an UniTask to enable the cancellation, unless you have any special reason. That’s because you must ensure that all UniTasks have finished, regardless of whether they were canceled or completed successfuly when they are no longer needed.

To understand why we must ensure an unnecessary UniTask finishes its process, take a look at an example.

Bad example
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SampleManager : MonoBehaviour
{
	private static int count = 0;
	
	private async UniTaskVoid Start()
	{
		count++;
		_ = RepeatMessageAsync($"Message from scene {count}");
		await UniTask.Delay(TimeSpan.FromSeconds(1));
		SceneManager.LoadScene(SceneManager.GetActiveScene().name);
	}

	private async UniTask RepeatMessageAsync(string message)
	{
		while (true)
		{
			Debug.Log(message);
			await UniTask.Delay(TimeSpan.FromSeconds(1));
		}
	}
}
C#

The RepeatMessageAsync method is an UniTask that shows a specified message every 1 second indefinitely. In the Start method, this method is called and the current scene is reloaded in 1 second. Additionally, the count static field is defined in order to determine which scene the method is called in.

If you run the above code, you will get the following result.

As you can see from the result, even if a scene transition occurs, a process of an UniTask doesn’t finish. The number of running UniTasks increases indefinitely as a new scene is loaded. Resource leak defenitely occurs here, and hence the application may crash due to memory exhaustion.

The thing is that the lifespan of an UniTask has nothing to do with that of the GameObject in which the UniTask is called. Even if a new scene is loaded and a GameObject in which an UniTask is called is destroyed, the UniTask will not disappear. Therefore, we must ensure that all running UniTasks terminate when thery become unnecessary in order to avoid resource leak.

The above code can be modified as follows:

Good code
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SampleManager : MonoBehaviour
{
	private static int count = 0;
	private async UniTaskVoid Start()
	{
		count++;
		_ = RepeatMessageAsync($"Message from scene {count}", this.GetCancellationTokenOnDestroy());
		await UniTask.Delay(TimeSpan.FromSeconds(1));
		SceneManager.LoadScene(SceneManager.GetActiveScene().name);
	}

	private async UniTask RepeatMessageAsync(string message, CancellationToken cancellationToken)
	{
		while (true)
		{
			Debug.Log(message);
			await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cancellationToken);
		}
	}
}
C#

In this code, the RepeatMessageAsync method can be canceled by a CancellationToken, and it is given a CancellationToken generated by the GetCancellationTokenOnDestroy method is when called. Thus when a new scene is loaded, the SampleManager object is destroyed and the CancellationToken bound to that object is canceled, which ensures the UniTask that was called from the old scene terminates. As you can see from the result, an old process terminates when the scene switches.

Again, we must ensure that all running UniTasks terminate their processes when they are no longer needed. If any UniTask whose process will never finish exists, the number of running UniTask may be enormous, which leads the application to crush, or unintended behavior may be caused by UniTasks that exist unintentionally. Even if an UniTask was designed to run indefinately in a specific scene without cancellation, a CancellationToken must be provided in order to ensure that it will be absolutely canceled when the scene finishes.

UniTask Tracker

Even if you strive to ensure all UniTasks are completed or canceled appropriately, it is understandable to let some UniTasks remain unintentionally. Here, you can check if there are any UniTasks that continue their processes unintentionally by UniTask Tracker.

Open UniTask Tracker by selecting Window/UniTaskTracker on the top menu, enable “Enable AutoReload” and “Enable Tracking,” and run the bad code and the good code.

The result of the code that doesn’t ensure that unnecessary UniTasks finish
The result of the code that ensures that unnecessary UniTasks finish

The number of UniTasks with the “Pending” state increases for the bad code while an old UniTask transitions to the “Canceled” state and terminates at the same time an new UniTask with the “Pending” state appears for the good code.

You should use UniTask Tracker regularly to check if there are any UniTasks that don’t terminate appropriately when you utilize UniTask for your development.

Exercise

Cancel an UniTask with CancellationTokenSource

Modify the following program to cancel Tasks A and Task B when the spacekey is pressed and cancel Task C when the returnkey is pressed. You use the CancellationTokenSource class to generate a CancellationToken object.

The StartTaskAsync method is an UniTask that never finishes until it is canceled. The UniTask.DelayFrame method is an UniTask that inserts delay corresponding to a specified frame counts and provided by UniTask library.

C#
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class SampleManager : MonoBehaviour
{
	private void Start()
	{
		_ = StartTaskAsync("Task A", default);
		_ = StartTaskAsync("Task B", default);
		_ = StartTaskAsync("Task C", default);
	}

	private void Update()
	{
		if (Input.GetKeyDown(KeyCode.Space))
		{
			Debug.Log("Spacekey pushed!");
		}
		if (Input.GetKeyDown(KeyCode.Return))
		{
			Debug.Log("Returnkey pushed!");
		}
	}

	private async UniTask StartTaskAsync(string name, CancellationToken cancellationToken)
	{
		try
		{
			Debug.Log($"{name} started...");
			while (true)
			{
				await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
			}
		}
		catch (OperationCanceledException)
		{
			Debug.Log($"{name} canceled!");
			throw;
		}
	}
}
C#
Sample result

Convert an UniTask into a CancellationToken

Modify the program in the previous question to cancel UniTasks with CancellationToken objects obtained from UniTasks without ones from the CancellationTokenSource instance. That is, you replace the process of deciding whether the key is being pressed in the Update method with UniTasks, from which you obtain CancellationToken objects.

Sample Answers

Cancel an UniTask with CanellationTokenSource
C#
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class SampleManager : MonoBehaviour
{
	CancellationTokenSource spacekeyTokenSource = new();
	CancellationTokenSource returnkeyTokenSource = new();

	private void Start()
	{
		_ = StartTaskAsync("Task A", spacekeyTokenSource.Token);
		_ = StartTaskAsync("Task B", spacekeyTokenSource.Token);
		_ = StartTaskAsync("Task C", returnkeyTokenSource.Token);
	}

	private void Update()
	{
		if (Input.GetKeyDown(KeyCode.Space))
		{
			Debug.Log("Spacekey pushed!");
			spacekeyTokenSource.Cancel();
		}
		if (Input.GetKeyDown(KeyCode.Return))
		{
			Debug.Log("Returnkey pushed!");
			returnkeyTokenSource.Cancel();
		}
	}

	private void OnDestroy()
	{
		spacekeyTokenSource?.Cancel();
		spacekeyTokenSource?.Dispose();
		returnkeyTokenSource?.Cancel();
		returnkeyTokenSource?.Dispose();
	}

	private async UniTask StartTaskAsync(string name, CancellationToken cancellationToken)
	{
		try
		{
			Debug.Log($"{name} started...");
			while (true)
			{
				await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
			}
		}
		catch (OperationCanceledException)
		{
			Debug.Log($"{name} canceled!");
			throw;
		}
	}
}
C#

Maker sure to cancel the CancellationTokenSource (to ensure that the unnecessary UniTasks stop) and dispose of it (because the CancellationTokenSource class implements the IDisposable interface).

Convert an UniTask to a CancellationToken
C#
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class SampleManager : MonoBehaviour
{
	private void Start()
	{
		var spacekeyToken = WaitUntilSpacekeyPushed(this.GetCancellationTokenOnDestroy()).ToCancellationToken();
		var returnkeyToken = WaitUntilReturnkeyPushed(this.GetCancellationTokenOnDestroy()).ToCancellationToken();
		_ = StartTaskAsync("Task A", spacekeyToken);
		_ = StartTaskAsync("Task B", spacekeyToken);
		_ = StartTaskAsync("Task C", returnkeyToken);
	}

	private async UniTask WaitUntilSpacekeyPushed(CancellationToken cancellationToken)
	{
		while (!Input.GetKeyDown(KeyCode.Space))
		{
			await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
		}
	}
	
	private async UniTask WaitUntilReturnkeyPushed(CancellationToken cancellationToken)
	{
		while (!Input.GetKeyDown(KeyCode.Return))
		{
			await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
		}
	}

	private async UniTask StartTaskAsync(string name, CancellationToken cancellationToken)
	{
		try
		{
			Debug.Log($"{name} started...");
			while (true)
			{
				await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
			}
		}
		catch (OperationCanceledException)
		{
			Debug.Log($"{name} canceled!");
			throw;
		}
	}
}
C#

Use the GetCancellationTokenOnDestroy method when calling the two UniTasks that are converted into CancellationTokens in order to ensure that these UniTasks terminate when they are no longer necessary.

Comments