0
votes

I printed which thread executes each called method in the following program and the results are very strange. I expected all asynchronous calls to be executed by a threadpool thread because @Jon Skeet mentioned that asynchronous calls use ThreadPool threads and that thread pool threads are background threads.

The thread id shows that the same thread that executes the Main method executes all called methods except for DisplayResult which is not an asynchronous method.

How is it that each call of DisplayResult, which is not an asynchronous method, is executed by a threadpool thread where as actual asynchronous methods when called are executed by a non-background thread, the same non-background thread that executed the Main method? And how many background threads and non-background threads should there be in this program?

public static async Task Main()
        {
            Console.WriteLine($"Main method: The thread executing this task is {Thread.CurrentThread.Name}, {Thread.CurrentThread.ManagedThreadId}");

            if (Thread.CurrentThread.IsBackground)
            {
                Console.WriteLine($"And it is a background thread.");

            }
            else
            {
                Console.WriteLine("And it is a non-background thread");
            }

            Console.WriteLine();

            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Console.WriteLine("It is a ThreadPoolThread");
            }
            else
            {
                Console.WriteLine("It is a non-ThreadPoolThread");
            }

            Task task1 = ProcessReadWriteAsync(@"/tmp/temp1Write.txt");

            Task task2 = ProcessReadWriteAsync(@"/tmp/temp2Write.txt");

            await task1;
            await task2;
        }

        public static async Task ProcessReadWriteAsync(string filePath)
        {
            Console.WriteLine($"ProcessReadWriteAsync: The thread executing this task is {Thread.CurrentThread.Name}, {Thread.CurrentThread.ManagedThreadId}");
            if (Thread.CurrentThread.IsBackground)
            {
                Console.WriteLine($"And it is a background thread.");

            }
            else
            {
                Console.WriteLine("And it is a non-background thread");
            }

            Console.WriteLine();

            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Console.WriteLine("It is a ThreadPoolThread");
            }
            else
            {
                Console.WriteLine("It is a non-ThreadPoolThread");
            }

            try
            {
            await ReadWriteAsync(filePath);

            } catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            } finally
            {
                Console.WriteLine();
            }
        }

        public static async Task ReadWriteAsync(string path, string text)
        {
            Console.WriteLine($"ReadWriteAsync 2 parameters: The thread executing this task is {Thread.CurrentThread.Name}, {Thread.CurrentThread.ManagedThreadId}");

            if (Thread.CurrentThread.IsBackground)
            {
                Console.WriteLine($"And it is a background thread.");

            }
            else
            {
                Console.WriteLine("And it is a non-background thread");
            }

            Console.WriteLine();

            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Console.WriteLine("It is a ThreadPoolThread");
            }
            else
            {
                Console.WriteLine("It is a non-ThreadPoolThread");
            }

            FileStream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, useAsync: true);

                byte[] buffer = new byte[0x1000];

                int noOfCharactersRead = await stream.ReadAsync(buffer, 0, buffer.Length);

                DisplayResult(buffer: buffer);
        }

        public static async Task ReadWriteAsync(string path)
        {
            Console.WriteLine($"ReadWriteAsync 1 parameters: The thread executing this task is {Thread.CurrentThread.Name}, {Thread.CurrentThread.ManagedThreadId}");
            if (Thread.CurrentThread.IsBackground)
            {
                Console.WriteLine($"And it is a background thread.");

            }
            else
            {
                Console.WriteLine("And it is a non-background thread");
            }

            Console.WriteLine();

            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Console.WriteLine("It is a ThreadPoolThread");
            }
            else
            {
                Console.WriteLine("It is a non-ThreadPoolThread");
            }

            await ReadWriteAsync(path, "");
        }

        private static void DisplayResult(byte[] buffer)
        {
            Console.WriteLine($"DisplayResult: The thread executing this method is {Thread.CurrentThread.Name}, {Thread.CurrentThread.ManagedThreadId}");
            if (Thread.CurrentThread.IsBackground)
            {
                Console.WriteLine($"And it is a background thread.");

            }
            else
            {
                Console.WriteLine("And it is a non-background thread");
            }

            Console.WriteLine();

            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Console.WriteLine("It is a ThreadPoolThread");
            }
            else
            {
                Console.WriteLine("It is a non-ThreadPoolThread");
            }

            string DecodedText = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

            string[] strings = DecodedText.Split('\n');

            for (int index = 0; index < strings.Length;index++)
            {
                Console.WriteLine(strings[index]);
            }
        }

Output:

Main method: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
ProcessReadWriteAsync: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
ReadWriteAsync 1 parameters: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
ReadWriteAsync 2 parameters: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
ProcessReadWriteAsync: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
ReadWriteAsync 1 parameters: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
ReadWriteAsync 2 parameters: The thread executing this task is , 1
And it is a non-background thread

It is a non-ThreadPoolThread
DisplayResult: The thread executing this method is , 4
And it is a background thread.

It is a ThreadPoolThread
1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               117152
5. msdn.microsoft.com/library/hh524395.aspx                68959
6. msdn.microsoft.com/library/ms404677.aspx               197325
7. msdn.microsoft.com                                     42972
8. msdn.microsoft.com/library/ff730837.aspx               146159
9. msdn.microsoft.com/library/hh191443.aspx                83732
10. msdn.microsoft.com/library/aa578028.aspx               205273
11. msdn.microsoft.com/library/jj155761.aspx                29019
12. msdn.microsoft.com/library/hh290140.aspx               117152
13. msdn.microsoft.com/library/hh524395.aspx                68959
14. msdn.microsoft.com/library/ms404677.aspx               197325
15. msdn.microsoft.com                                     42972
16. msdn.microsoft.com/library/ff730837.aspx               146159
17. msdn.microsoft.com/library/hh191443.aspx                83732
18. msdn.microsoft.com/library/aa578028.aspx               205273
19. msdn.microsoft.com/library/jj155761.aspx                29019
20. msdn.microsoft.com/library/hh290140.aspx               117152
21. msdn.microsoft.com/library/hh524395.aspx                68959
22. msdn.microsoft.com/library/ms404677.aspx               197325
23. msdn.microsoft.com                                     42972
24. msdn.microsoft.com/library/ff730837.aspx               146159
25. msdn.microsoft.com/library/hh191443.aspx                83732
26. msdn.microsoft.com/library/aa578028.aspx               205273
27. msdn.microsoft.com/library/jj155761.aspx                29019
28. msdn.microsoft.com/library/hh290140.aspx               117152
29. msdn.microsoft.com/library/hh524395.aspx                68959
30. msdn.microsoft.com/library/ms404677.aspx               197325
31. msdn.microsoft.com                                     42972
32. msdn.microsoft.com/library/ff730837.aspx               146159
33. msdn.microsoft.com/library/hh191443.aspx                83732
34. msdn.microsoft.com/library/aa578028.aspx               205273
35. msdn.microsoft.com/library/jj155761.aspx                29019
36. msdn.microsoft.com/library/hh290140.aspx               117152
37. msdn.microsoft.com/library/hh524395.aspx                68959
38. msdn.microsoft.com/library/ms404677.aspx               197325
39. msdn.microsoft.com                                     42972
40. msdn.microsoft.com/library/ff730837.aspx               146159
41. msdn.microsoft.com/library/hh191443.aspx                83732
42. msdn.microsoft.com/library/aa578028.aspx               205273
43. msdn.microsoft.com/library/jj155761.aspx                29019
44. msdn.microsoft.com/library/hh290140.aspx               117152
45. msdn.microsoft.com/library/hh524395.aspx                68959
46. msdn.microsoft.com/library/ms404677.aspx               197325
47. msdn.microsoft.com                                     42972
48. msdn.microsoft.com/library/ff730837.aspx               146159
49. msdn.microsoft.com/library/hh191443.aspx                83732
50. msdn.microsoft.com/library/aa578028.aspx               205273
51. msdn.microsoft.com/library/jj155761.aspx                29019
52. msdn.microsoft.com/library/hh290140.aspx               117152
53. msdn.microsoft.com/library/hh524395.aspx                68959
54. msdn.microsoft.com/library/ms404677.aspx               197325
55. msdn.microsoft.com                                     42972
56. msdn.microsoft.com/library/ff730837.aspx               146159
57. msdn.microsoft.com/library/hh191443.aspx                83732
58. msdn.microsoft.com/library/aa578028.aspx               205273
59. msdn.microsoft.com/library/jj155761.aspx                29019
60. msdn.microsoft.com/library/hh290140.aspx               117152
61. msdn.microsoft.com/library/hh524395.aspx                68959
62. msdn.microsoft.com/library/ms404677.aspx               197325
63. msdn.microsoft.c

DisplayResult: The thread executing this method is , 5
And it is a background thread.

It is a ThreadPoolThread
1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               117152
5. msdn.microsoft.com/library/hh524395.aspx                68959
6. msdn.microsoft.com/library/ms404677.aspx               197325
7. msdn.microsoft.com                                     42972
8. msdn.microsoft.com/library/ff730837.aspx               146159
9. msdn.microsoft.com/library/hh191443.aspx                83732
10. msdn.microsoft.com/library/aa578028.aspx               205273
11. msdn.microsoft.com/library/jj155761.aspx                29019
12. msdn.microsoft.com/library/hh290140.aspx               117152
13. msdn.microsoft.com/library/hh524395.aspx                68959
14. msdn.microsoft.com/library/ms404677.aspx               197325
15. msdn.microsoft.com                                     42972
16. msdn.microsoft.com/library/ff730837.aspx               146159
17. msdn.microsoft.com/library/hh191443.aspx                83732
18. msdn.microsoft.com/library/aa578028.aspx               205273
19. msdn.microsoft.com/library/jj155761.aspx                29019
20. msdn.microsoft.com/library/hh290140.aspx               117152
21. msdn.microsoft.com/library/hh524395.aspx                68959
22. msdn.microsoft.com/library/ms404677.aspx               197325
23. msdn.microsoft.com                                     42972
24. msdn.microsoft.com/library/ff730837.aspx               146159
25. msdn.microsoft.com/library/hh191443.aspx                83732
26. msdn.microsoft.com/library/aa578028.aspx               205273
27. msdn.microsoft.com/library/jj155761.aspx                29019
28. msdn.microsoft.com/library/hh290140.aspx               117152
29. msdn.microsoft.com/library/hh524395.aspx                68959
30. msdn.microsoft.com/library/ms404677.aspx               197325
31. msdn.microsoft.com                                     42972
32. msdn.microsoft.com/library/ff730837.aspx               146159
33. msdn.microsoft.com/library/hh191443.aspx                83732
34. msdn.microsoft.com/library/aa578028.aspx               205273
35. msdn.microsoft.com/library/jj155761.aspx                29019
36. msdn.microsoft.com/library/hh290140.aspx               117152
37. msdn.microsoft.com/library/hh524395.aspx                68959
38. msdn.microsoft.com/library/ms404677.aspx               197325
39. msdn.microsoft.com                                     42972
40. msdn.microsoft.com/library/ff730837.aspx               146159
41. msdn.microsoft.com/library/hh191443.aspx                83732
42. msdn.microsoft.com/library/aa578028.aspx               205273
43. msdn.microsoft.com/library/jj155761.aspx                29019
44. msdn.microsoft.com/library/hh290140.aspx               117152
45. msdn.microsoft.com/library/hh524395.aspx                68959
46. msdn.microsoft.com/library/ms404677.aspx               197325
47. msdn.microsoft.com                                     42972
48. msdn.microsoft.com/library/ff730837.aspx               146159
49. msdn.microsoft.com/library/hh191443.aspx                83732
50. msdn.microsoft.com/library/aa578028.aspx               205273
51. msdn.microsoft.com/library/jj155761.aspx                29019
52. msdn.microsoft.com/library/hh290140.aspx               117152
53. msdn.microsoft.com/library/hh524395.aspx                68959
54. msdn.microsoft.com/library/ms404677.aspx               197325
55. msdn.microsoft.com                                     42972
56. msdn.microsoft.com/library/ff730837.aspx               146159
57. msdn.microsoft.com/library/hh191443.aspx                83732
58. msdn.microsoft.com/library/aa578028.aspx               205273
59. msdn.microsoft.com/library/jj155761.aspx                29019
60. msdn.microsoft.com/library/hh290140.aspx               117152
61. msdn.microsoft.com/library/hh524395.aspx                68959
62. msdn.microsoft.com/library/ms404677.aspx               197325
63. msdn.microsoft.c
1
"public static async Task Main()" is more of a syntax sugar to enable await in Main method, so its execution can be different from your expectation (like your code observed).Lex Li
Ignore async. Ignore any thoughts about threads. Read your code as if it's single-threaded and entirely normal, right up until you hit an await and whatever is to the right of that has been evaluated to produce a Task (that includes entering any method calls to the right of it). Congratulations, that's exactly what happens. How far do you get in your chain of method calls before you hit that first await?Damien_The_Unbeliever
I expected all asynchronous calls to be executed by a threadpool thread because @Jon Skeet mentioned that asynchronous calls use ThreadPool threads this is absolutely false. I doubt Jon Skeet actually said this because it is not correct, but if he did, please link to the question/answer so it can be corrected. It's very simple, if you're not using Task.Run() with asynchronous calls, you're not using threadpool threads. Everything is running on one thread. Unless of course, the async method doesn't respect API etiquette and calls Task.Run() or spawns other threads in the implementation.Patrick Tucci
Not to put too fine a point on it, @MyWrathAcademia, but I'd suggest you approach the comments/answers as if you've never heard anything about async/await, rather than thinking you mostly understand it but just need a few details clarified. There are many misconceptions here.Damien_The_Unbeliever
@MyWrathAcademia because there is inherent waiting involved with I/O bound code. When your code requests bytes from a file, it can do other things while it's waiting, like attempt to read from another file, even without a second thread. These articles from Eric Lippert (an old article about C# 5.0 CTP, but still a good read) and Steven Cleary are must-reads.Patrick Tucci

1 Answers

3
votes

All asynchronous methods start running on the current thread. Nothing different happens until you hit an await that acts on an incomplete Task. But whatever you're feeding to await has to actually return a value (a Task) before await actually does anything.

So lets walk through exactly what's happening:

  1. Main runs until it calls ProcessReadWriteAsync(@"/tmp/temp1Write.txt")
  2. ProcessReadWriteAsync(string) runs until it calls ReadWriteAsync(filePath)
  3. ReadWriteAsync(string) runs until it calls ReadWriteAsync(path, "")
  4. ReadWriteAsync(string,string) runs until it calls stream.ReadAsync(buffer, 0, buffer.Length)
  5. stream.ReadAsync() does some magic until it returns an incomplete Task
  6. The await in ReadWriteAsync(string,string) see that incomplete Task and returns a new incomplete Task, with the rest of the method signed up as a continuation of that Task
  7. ReadWriteAsync(string) returns an incomplete Task
  8. ProcessReadWriteAsync(string) returns an incomplete Task
  9. There is no await yet in Main(), so execution continues there.
  10. Main() calls ProcessReadWriteAsync(@"/tmp/temp2Write.txt"), which starts that whole process again.

All of that happens on the same thread.

When you finally call await task1, it tells it to pause execution until the Task is completed. At this point, none of your code is running anymore.

When stream.ReadAsync() finally completes, its Task is set to Completed and the rest of ReadWriteAsync(string,string) runs until completion, which then triggers ReadWriteAsync(string) to run to completion, which then triggers ProcessReadWriteAsync(string) to run to completion. All of this happens on a background thread, which is why you see DisplayResult running on a background thread.

Once task1 is set to completed, your Main() method resumes.

Keep in mind that in an application with a synchronization context, like a UI application or ASP.NET (not Core), the continuations do not happen on a background thread. They will happen on the same thread they started on. So in those cases, the continuations would not even start until you hit await task1. However, you can tell it that you don't need them to return to the same synchronization context with .ConfigureAwait(false).