Using Random.Range prevents an async task from finishing

  async Task RunDownloadAsync()
     {
         Debug.Log("Starting Download");
         var result = await Task.Run(() => Download());
         Debug.Log(result);
     }
  
     string Download()
     {
         Debug.Log("Downloading");
    
      //   Debug.Log(Random.Range(0, 100).ToString());
      
         Debug.Log("Download Complete");
      
         return "a cool movie";
     }

Works exactly as expected:
8725503--1180167--upload_2023-1-12_18-30-4.png

When I uncomment the line where I print out a Random.Range number it stops right there. It doesn’t even print it out. When calling the method directly (result = Download()) it also works. But somehow Random.Range interferes with the task.

8725503--1180170--upload_2023-1-12_18-33-18.png

Can someone enlighten me (and gain the badge of being smarter than ChatGPT which is also confused why it’s not working :wink: )

Thank you!

I don’t see a “Done” at the end of your top screenshot… is that just because you added the code after snapping the screenshot? If not, then it seems to me you should see a “Done” and thus something isn’t working exactly as expected even in the first one…

Task.Run runs the function on another thread

Random.Range is probably not thread safe, which is true for most of unity

Sorry my bad, I had changed it the line to Debug.Log(result). So without random.range it does work as expected.

Thanks, at least I have some info on that now. I’ll just make sure to be extra careful with async and test every line.

Why not just download via standard old coroutine-driven UnityWebRequest?

It will probably end up being much easier to support into the future.

(I’m obviously only addressing the SPECIFIC use I see above, but in general, coroutines are always going to have less friction / complications.)

That was just an example, I’m trying to get really good with async and investigate every issue I encounter. Hence no coroutines here :slight_smile:

Not just probably – it explicitly errors out! I guess the exception didn’t show up in the log window (or maybe errors were filtered out?)

This does throw an explicit error in the console:

    async void StartRequest()
    {
        await RunDownloadAsync();  
    }


    async Task RunDownloadAsync()
    {
        Debug.Log("Starting Download");

        Debug.Log(Random.Range(0, 10002));

        var result = await Task.Run(() => Download());

        Debug.Log("done");
    }

   

    string Download()
    {
        Debug.Log("Downloading");

        // simulate a long running task by doing a bunch of math
        for (int i = 0; i < 1000000000; i++)
        {
            var x = i * i;
        }

        Debug.Log(Random.Range(0, 12));

        Debug.Log("Download Complete");

        return "a cool movie";
    }

8729148--1181037--upload_2023-1-13_23-10-29.png

This does not throw an error but will never call Random.Range or anything that follows it. The app doesn’t crash and the main thread keeps running:

 void StartRequest()
    {
        RunDownloadAsync();
    }

    async Task RunDownloadAsync()
    {
        Debug.Log("Starting Download");

        Debug.Log(Random.Range(0, 10002));

        var result = await Task.Run(() => Download());
       
        Debug.Log("done");
    }


    string Download()
    {
        Debug.Log("Downloading");

        // simulate a long running task by doing a bunch of math
        for (int i = 0; i < 1000000; i++)
        {
            var x = i * i;
        }

        //log a random int
        Debug.Log(Random.Range(0, 12));

        Debug.Log("Download Complete");

        return "a cool movie";
    }

8729148--1181040--upload_2023-1-13_23-15-34.png

Playing around with await some more, I’m confused why this doesn’t use multi threading at all (game freezes until the method is done), but if I use the method below with running a task it works perfectly.

   void StartRequest()
    {
        RunDownloadAsync();
        Debug.Log("Task started but continuing with other stuff in the mean time");
    }


    async Task RunDownloadAsync()
    {
        Debug.Log("Starting Download");

     
        await Download();
       // await Task.Run(() => Download());

        Debug.Log("done");
    }

 

    async Task<string> Download()
    {
        Debug.Log("Downloading");

        // simulate a long running task by doing a bunch of math
        for (int i = 0; i < 1000000000; i++)
        {
            var x = i * i;
        }

        Debug.Log("Download Complete");

        return "a cool movie";
    }

8729148--1181043--upload_2023-1-13_23-40-3.png

Async Await alone isn’t actually multithreaded, it’s a coroutine that is running on the main thread

As you can see here, Async Await and Coroutine behaviour is pretty much the same
Await Task.Yield of the async function and Yield return null of the coroutine both yield until the next frame, so the loops are being run over multiple frames.

    void Start()
    {
        CountFramesAsync();
        StartCoroutine(CountFramesCoroutine());
    }

    public async void CountFramesAsync()
    {
        for (int i = 0; i < 20; i++)
        {
            Debug.Log($"Async {Time.frameCount}");
            await Task.Yield();
        }
    }

    public IEnumerator CountFramesCoroutine()
    {
        for (int i = 0; i < 20; i++)
        {
            Debug.Log($"Coroutine {Time.frameCount}");
            yield return null;
        }
    }

Without yielding in some form, your async function is just going to run like a normal function in the main thread until it is finished, the same is true with a coroutine. If you’re doing something that takes long, it will freeze the main thread, so you either have to break it up into smaller pieces by yielding, or you have to run that task in a different thread.

Task.Run is starting a new thread and running a function in that thread, which means it doesn’t need to yield to not freeze the main thread.

When you await the Task.Run your async function is yielding until the function in that other thread has finished, before it picks up where it left off.

Thanks, that really helped to clear things up!