Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Is there something like StopCoroutine but for Tasks?

Discussion in 'Scripting' started by III111III, Aug 16, 2023.

  1. III111III

    III111III

    Joined:
    Nov 8, 2017
    Posts:
    12
    Hi,

    When I use the CancellationTokenSource.Cancel, it stops all the Tasks.
    How can I stop a unique Task? (Something like the StopCoroutine Method).

    Thanks.
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    async
    methods generally should all take a cancellation token as a parameter. Then said method should internally be set up to opt-out when the token is cancelled. There's various ways to hook into this, and tons of tutorials out there on how to do so as this is regular C# stuff.

    So if you have your own async methods, they need to take a token and be set up to end early when the token is raised.
     
  3. III111III

    III111III

    Joined:
    Nov 8, 2017
    Posts:
    12
    Thanks.

    I added a token to the Task like this :
    Task.Run(() => MethodAsync(), cts.Token);
    I'll search on Google to see how to add a token to the async methods.

    I guess it's possible to reuse an async method that was canceled.
    When I cancel a Task, it's not possible to reuse it.
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Like I said, your async method itself should take a cancellation token, and back out if the token is raised. Somewhere in this link there's this code roughly:
    Code (CSharp):
    1. public static async Task LongRunningTask(CancellationToken token)
    2. {
    3.     for (int i = 0; i < count; i++)
    4.     {
    5.         // some task
    6.        
    7.         if (token.IsCancellationRequested)
    8.         {
    9.             // return or throw error
    10.         }
    11.     }
    12. }
    There's a lot more to it than that, of course. Lots of reading ahead of you. Took me a while to get my head around it.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Am I correct that this is analogous to just passing a delegate into a coroutine to test if quit?

    Code (csharp):
    1. IEnumerator MyCoRo( System.Func<bool> ShouldQuit)
    2. {
    3.   while( true)
    4.   {
    5.     if (ShouldQuit()) yield break;
    6.  
    7.     yield return null;
    8.   }
    9. }
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Roughly analagous I suppose? Cancellation tokens are a bit more lightweight, but also managed. You need to dispose the sources once you're done with them, but you can also combine multiple tokens into a single source, as a good way to 'bubble up' cancellations.

    I mostly use
    async
    code because, A: My code looks a lot cleaner, not having to
    StartCoroutine
    everything, B: they can be independant of game objects, and C: it makes it easier to interface with operations like
    System.IO.ReadAllTextAsync
    , etc.

    But you do end up with a bit more boilerplate if you go down the route of cancellation tokens. I'm still wrapping my head around how to best use them, to be honest.
     
    Kurt-Dekker likes this.
  7. III111III

    III111III

    Joined:
    Nov 8, 2017
    Posts:
    12
    Is it possible to pause a task instead of canceling it?
    I'm able to launch a Task several times but when I cancel it, I can't launch it again.
    This is a little code I attach to an empty game object to test cancel and see the result in the console:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Threading.Tasks;
    3. using System.Threading;
    4.  
    5. public class TaskCancellation : MonoBehaviour
    6. {
    7.     private readonly CancellationTokenSource cTokenSource = new();
    8.  
    9.     private void Update()
    10.     {
    11.         // Input Manager
    12.         if(Input.anyKeyDown)
    13.         {
    14.             // Run Task (Space)
    15.             if(Input.GetKeyDown(KeyCode.Space))
    16.             {
    17.                 Task.Run(() => MethodAsync(cTokenSource.Token), cTokenSource.Token);
    18.             }
    19.  
    20.             // Cancel Task (LeftShift)
    21.             if(Input.GetKeyDown(KeyCode.LeftShift))
    22.             {
    23.                 cTokenSource.Cancel();
    24.             }
    25.         }
    26.     }
    27.  
    28.     // Asynchronous Method
    29.     private async Task MethodAsync(CancellationToken token)
    30.     {
    31.         // Iteration
    32.         for(int i = 1; i <= 10; i++)
    33.         {
    34.             // Write on the console every second (10 times)
    35.             await Task.Delay(1_000);
    36.             Debug.Log("<color=yellow>MethodAsync Iteration : </color>" + i + "\n");
    37.  
    38.             // Cancel Task if requested
    39.             if(token.IsCancellationRequested)
    40.             {
    41.                 Debug.Log("<color=red>MethodAsynd canceled</color>\n");
    42.                 return;
    43.             }
    44.         }
    45.  
    46.         // Console : Task finished
    47.         Debug.Log("<color=red>MethodAsynd finished</color>\n");
    48.     }
    49. }
     
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Tasks nor co-routines aren't designed to stop and start again. That's not their purpose.

    What is your actual end goal here?
     
    Last edited: Aug 17, 2023
  9. III111III

    III111III

    Joined:
    Nov 8, 2017
    Posts:
    12
    Just doing some tests and learning.
    Thanks.
    I like coroutines but since everybody in the tutorials say that async methods are better than coroutines, I started to learn async await Task.

    I created a bool as a switch and with if else, I can make the Task stop and start :

    Code (CSharp):
    1.         private void SwitchTaskOnOff()
    2.         {
    3.             // if Task isn't running : Create a CancellationTokenSource and Run Task
    4.             if(isTaskRunning == false)
    5.             {
    6.                 cTokenSource = new CancellationTokenSource();
    7.                 Task.Run(() => MethodAsync(cTokenSource.Token), cTokenSource.Token);
    8.             }
    9.             // if Task is running : Cancel Task
    10.             else if(isTaskRunning == true)
    11.             {
    12.                 cTokenSource.Cancel();
    13.             }
    14.  
    15.             // Switch Task On/Off
    16.             isTaskRunning = !isTaskRunning;
    17.         }
     
  10. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I don't think any is better. They have their pros and cons, and their time and place

    I would watch this if you haven't already:


    And if you do want to pursue more async, I would recommend UniTask: https://github.com/Cysharp/UniTask
     
    Ryiah, SisusCo and Anthiese like this.
  11. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,104
    Main differences in short:

    Coroutines:
    1. Automatically aborted when the MonoBehaviour is destroyed or set inactive
    2. Easy to stop at will
    3. Simpler to use in general
    Tasks:
    1. Can be run on background threads
    2. Can return a value
    3. Does not require a MonoBehaviour instance
    My advise is to just keep on using coroutines as long as it feels intuitive.
    Then if you need to return a value or perform work in parallel consider using Tasks instead.