Home > other >  What determines the use of other threads besides the main thread when working with asynchronous task
What determines the use of other threads besides the main thread when working with asynchronous task

Time:10-09

I was playing around with async tasks in Unity when I noticed some behavior I couldn't explain. While printing out some values on the console to see what was going on, I noticed something about the thread IDs of the various scenarios I was playing around with. The code below includes the following:

  • Two tests within a "fire and forget" async void to get everything going
  • A task awaiting another task (a small delay)
  • And a few debug logs to monitor the thread IDs of each started task

TEST 1 within the Start method just awaits the SimpleTask and gives the following output:

  • Task started on thread: 1

  • SimpleTask before delay on thread: 1

  • SimpleDelay on thread: 1

  • SimpleTask after delay on thread: 1

TEST 2 on the other hand leads to this:

  • Task started on thread: 1

  • SimpleTask before delay on thread: 25

  • SimpleDelay on thread: 25

  • SimpleTask after delay on thread: 26

Why does the second test result in the use of other threads? My understanding of this line is that it simply wraps the first test in an extra layer of an async task. I know there's no point in doing that, but I can't figure out why it completely changes the results of the initial test. It's not like the first test is running synchronously. I would like to understand what is going on here, the more explanation the better.

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class TestAsyncTask : MonoBehaviour
{

    private async void Start()
    {
        Debug.Log($"Task started on thread: {Thread.CurrentThread.ManagedThreadId}");

        // TEST 1
        await SimpleTaskAsync();

        // TEST 2
        await Task.Factory.StartNew(async () => await SimpleTaskAsync());
    }

    private async Task SimpleTaskAsync()
    {
        Debug.Log($"SimpleTask before delay on thread: {Thread.CurrentThread.ManagedThreadId}");
        await SimpleDelayAsync();
        Debug.Log($"SimpleTask after delay on thread: {Thread.CurrentThread.ManagedThreadId}");
    }

    private async Task SimpleDelayAsync()
    {
        Debug.Log($"SimpleDelay on thread: {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(1000);
    }
}

Additional specs (I doubt they matter but here are they anyway):

  • Unity version 2021.3.11f1
  • API compatibility level: .NET Framework

CodePudding user response:

await by default will capture the current context (SynchronizationContext or TaskScheduler) and will resume executing the async method in that context; if there is no context, then they will run on a thread pool thread. I recommend you read my async intro which goes into more detail.

In this case, I assume the code is executed within a UI context (on the UI thread). UI frameworks provide a SynchronizationContext so that code after the await resumes on the UI thread.

My understanding of this line is that it simply wraps the first test in an extra layer of an async task.

The reason the second code doesn't use the UI thread is because of the StartNew (which, as I describe on my blog, is dangerous; use Task.Run instead). Task.Run always executes its delegate on a thread pool thread, and StartNew (in this case) does, too. Thread pool threads do not have any context for await to capture, so the code after the await runs on any available thread pool thread.

  • Related