0

I understand the fundamentals of async/await however I see it mentioned a lot that await does not create a new thread. I can respect this since await could be using a hard disk I/O or network card or something besides a CPU where a new thread isn't desired.

What I'd like clarification on is whether, after the await is complete, the remainder of the async function is executed on a new thread? From what I can see in my own internal testing it does, as per the following code:

public static class Program
        {
            private static void Main(string[] args)
            {
                Thread.CurrentThread.Name = "ThisThread";
                AnAsyncMethod();
                while (true)
                {
                    print("External While loop. Current Thread: " + Thread.CurrentThread.Name);
                    Thread.Sleep(200);
                }
            }

            public static async void AnAsyncMethod()
            {
                for (int i = 0; i < 10; i++)
                {
                    FakeWork();
                    print("doing fake pre-await work. Current Thread: " + Thread.CurrentThread.Name);
                }
                await Task.Run(FakeWork);
                for (int i = 0; i < 10; i++)
                {
                    FakeWork();
                    print("doing fake post-await work. Current Thread: " + Thread.CurrentThread.Name);
                }
                print("hello");
            }

            public static void FakeWork()
            {
                Thread.Sleep(100);
            }

        }

From this example it seems the same thread is used in the async method until it encounters its first await, at which time control is returned to the caller and the program continues. When the await completes, a new separate thread is started to continue onward within the async method instead of marshaling the earlier thread and forcing it to continue itself. This new thread executes concurrently with the earlier thread which is now being used elsewhere.

Is this correct?

user4779
  • 645
  • 5
  • 14
  • 2
    https://stackoverflow.com/q/33821679/ should answer your question. – StuartLC Jan 12 '20 at 07:03
  • Try repeating your experiment, but using a WinForms or WPF application instead of a console application. Do you get different results? And why do you think that is? – Eric Lippert Jan 12 '20 at 07:49
  • @user4779 - Why did you just delete your other [question](https://stackoverflow.com/questions/59924672/how-to-safely-make-a-timeout-for-a-task)? – Enigmativity Jan 27 '20 at 03:15

2 Answers2

2

The behavior is different on a Console application to a WinForms application.

On the Console Application the continuation by default runs on the Default TaskScheduler, which runs the continuation of the Task on an available Worker Thread Pool. This will not happen under WinForms where the continuation is posted back to the UI thread.

Consider the following example:

    static async Task DoSomeThingAsync(string name)
    {
        Console.WriteLine($"{name} is starting on thread {Thread.CurrentThread.ManagedThreadId}");
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(1000);
            Console.WriteLine($"{name} is now on thread {Thread.CurrentThread.ManagedThreadId}");
        }

    }
    static async Task Main(string[] args)
    {
        var task1 = DoSomeThingAsync("Harry");
        var task2 = DoSomeThingAsync("Fred");
        var task3 = DoSomeThingAsync("Jack");

        Task[] MyTasks = new Task[3]{ task1, task2, task3 };

        await Task.WhenAll(MyTasks);

        Console.WriteLine("All tasks have completed.");
    }

Results in the console output:

Harry is starting on thread 1
Fred is starting on thread 1
Jack is starting on thread 1
Jack is now on thread 4
Fred is now on thread 5
Harry is now on thread 6
Fred is now on thread 8
Harry is now on thread 5
Jack is now on thread 6
Fred is now on thread 7
Harry is now on thread 6
Jack is now on thread 5
Harry is now on thread 7
Fred is now on thread 5
Jack is now on thread 6
Harry is now on thread 5
Jack is now on thread 6
Fred is now on thread 9
All tasks have completed.

You will notice that the actual thread that the code is executing on, jumps around, on my system I saw six different threads - your mileage will vary.

But what's important is that the first Console.WriteLine() always runs on the main thread, being thread one.

In a Console Application you do not need to use Task.Run() to get your CPU intensive tasks to run on a different thread pool. Simply await a Task - eg,

await Task.Yield();
Rowan Smith
  • 1,815
  • 15
  • 29
0

I have modified your code to show thread id.

class Program
    {
        static  void Main(string[] args)
        {
            System.Threading.Thread.CurrentThread.Name = "ThisThread";
            AnAsyncMethod();
            while (true)
            {
                Console.WriteLine("External While loop. Current Thread: " + $"{System.Threading.Thread.CurrentThread.Name}- {System.Threading.Thread.CurrentThread.ManagedThreadId}");
                System.Threading.Thread.Sleep(200);

            }
        }

        public static async void AnAsyncMethod()
        {
            for (int i = 0; i < 10; i++)
            {
                FakeWork();
                Console.WriteLine("doing fake pre-await work. Current Thread: " + $"{System.Threading.Thread.CurrentThread.Name}- {System.Threading.Thread.CurrentThread.ManagedThreadId}");
            }
            await System.Threading.Tasks.Task.Run(FakeWork);// Task.Run(FakeWork);
            for (int i = 0; i < 10; i++)
            {
                FakeWork();
                Console.WriteLine("doing fake post-await work. Current Thread: " + $"{System.Threading.Thread.CurrentThread.Name}- {System.Threading.Thread.CurrentThread.ManagedThreadId}");
            }
            Console.WriteLine("hello");
        }

        public static void FakeWork()
        {
            System.Threading.Thread.Sleep(100);
             StackTrace stackTrace = new StackTrace();

        Console.WriteLine($"Caller of Fakework method is  {stackTrace.GetFrame(1).GetMethod().Name}. Current Thread: " + $"{System.Threading.Thread.CurrentThread.Name}- {System.Threading.Thread.CurrentThread.ManagedThreadId}");

        }
    }

and I get this output output

Now you are getting post await in a separate thread. now this happens because

When it calls Task.Run, it queues the expensive CPU-bound operation, Fakework(), on the thread pool and receives a Task handle. Fakework() is eventually run concurrently on the next available thread

for more reference read async for cpu-bound method

https://learn.microsoft.com/en-us/dotnet/standard/async-in-depth

so async await job is to schedule task on the thread-pool and base on default scheduler algorithm it will use same thread or spin-off new thread

divyang4481
  • 1,584
  • 16
  • 32
  • yes and if you want to see that you can add below two line in your FakeWork method StackTrace stackTrace = new StackTrace(); Console.WriteLine($"Caller of Fakework method is {stackTrace.GetFrame(1).GetMethod().Name}. Current Thread: " + $"{System.Threading.Thread.CurrentThread.Name}- {System.Threading.Thread.CurrentThread.ManagedThreadId}"); – divyang4481 Jan 12 '20 at 07:58
  • I have update this in Fakework method code – divyang4481 Jan 12 '20 at 07:59
  • Okay that explains it thank you – user4779 Jan 12 '20 at 08:03