异步程序未异步运行的奇怪行为

Posted

技术标签:

【中文标题】异步程序未异步运行的奇怪行为【英文标题】:Strange behavior with asynchronous program not running asynchronously 【发布时间】:2020-07-28 16:04:29 【问题描述】:

我在下面的程序中打印了哪个线程执行每个调用的方法,结果很奇怪。我希望所有异步调用都由线程池线程执行,因为@Jon Skeet 提到异步调用使用 ThreadPool 线程并且线程池线程是后台线程。

线程 id 显示执行 Main 方法的同一线程执行所有调用的方法,但 DisplayResult 不是异步方法。

DisplayResult 的每个调用(不是异步方法)是如何由线程池线程执行的,而实际的异步方法在调用时是由非后台线程执行的,与执行主要方法?以及这个程序应该有多少个后台线程和非后台线程?

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]);
            
        

输出:

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

【问题讨论】:

"public static async Task Main()" 更像是在 Main 方法中启用 await 的语法糖,因此它的执行可能与您的预期不同(就像您观察到的代码一样)。 忽略async。忽略任何关于线程的想法。阅读您的代码,就好像它是单线程的并且完全正常,直到您点击 await 并且已评估其右侧的任何内容以生成 Task (包括在右侧输入任何方法调用)它)。恭喜,这正是发生的事情。在您第一次点击await 之前,您在方法调用链中走了多远? I expected all asynchronous calls to be executed by a threadpool thread because @Jon Skeet mentioned that asynchronous calls use ThreadPool threads 这绝对是错误的。我怀疑 Jon Skeet 实际上是这么说的,因为它不正确,但如果他这样做了,请链接到问题/答案,以便更正。这很简单,如果您不使用 Task.Run() 进行异步调用,那么您就没有使用线程池线程。一切都在一个线程上运行。当然,除非 async 方法不尊重 API 礼仪并调用 Task.Run() 或在实现中产生其他线程。 不要过分强调这一点,@MyWrathAcademia,但我建议您接近 cmets/answers,就好像您从未听说过有关 async/await 的任何信息,而不是主要考虑您理解它,但只需要澄清一些细节。这里有很多误解。 @MyWrathAcademia 因为 I/O 绑定代码存在固有的等待。当您的代码从文件请求字节时,它可以在等待时执行其他操作,例如尝试从另一个文件读取,即使没有第二个线程。这些来自Eric Lippert (an old article about C# 5.0 CTP, but still a good read) 和Steven Cleary 的文章是必读的。 【参考方案1】:

所有异步方法都开始在当前线程上运行。在您点击作用于不完整的Taskawait 之前,不会发生任何不同。但是无论你向await 提供什么,都必须在await 实际执行任何操作之前实际返回一个值(Task)。

让我们来看看到底发生了什么:

    Main 一直运行到它调用 ProcessReadWriteAsync(@"/tmp/temp1Write.txt") ProcessReadWriteAsync(string) 一直运行到它调用 ReadWriteAsync(filePath) ReadWriteAsync(string) 一直运行到它调用 ReadWriteAsync(path, "") ReadWriteAsync(string,string) 一直运行到它调用 stream.ReadAsync(buffer, 0, buffer.Length) stream.ReadAsync() 做了一些魔法,直到它返回一个不完整的 Task ReadWriteAsync(string,string) 中的await 看到不完整的Task 并且返回一个新的不完整Task,该方法的其余部分注册为该Task 的延续 ReadWriteAsync(string) 返回一个不完整的 Task ProcessReadWriteAsync(string) 返回一个不完整的 Task Main() 中还没有await,所以在那里继续执行。 Main() 调用ProcessReadWriteAsync(@"/tmp/temp2Write.txt"),这又开始了整个过程。

所有这些都发生在同一个线程上。

当你最终调用await task1 时,它会告诉它暂停执行,直到Task 完成。此时,您的任何代码都不再运行。

stream.ReadAsync() 最终完成时,它的Task 设置为CompletedReadWriteAsync(string,string) 的其余部分运行直到完成,然后触发ReadWriteAsync(string) 运行到完成,然后触发ProcessReadWriteAsync(string) 运行完成。所有这些都发生在后台线程上,这就是为什么您会看到 DisplayResult 在后台线程上运行。

一旦task1 设置为完成,您的Main() 方法就会恢复。

请记住,在具有同步上下文的应用程序中,例如 UI 应用程序或 ASP.NET(不是 Core),继续不会发生在后台线程上。它们将发生在他们开始的同一线程上。因此,在这些情况下,直到您点击 await task1 之后,延续才会开始。但是,您可以告诉它您不需要它们返回到与.ConfigureAwait(false) 相同的同步上下文。

【讨论】:

Async 现在更清晰了,谢谢。何时返回不完整的Task?是调用返回异步方法的 TaskResult 的瞬间吗?是一个不完整的Task Task 返回一个TaskResult - 这就是为什么只有stream.ReadAsync 返回一个不完整的Task 因为所有其他被调用的Async 方法只返回Task await 作用于由stream.ReadAsync 返回的不完整的Task 导致通过函数调用堆栈的ReadWriteAsync(string, string)ReadWriteAsync(string)ProcessReadWriteAsync(string) 的帧返回(即最后一个先进先出),就像在递归中一样?为什么方法中的其余代码在后台线程上运行完成?这是一个武断的决定,还是有特定原因导致挂起方法中的剩余代码不在非后台线程上运行? “不完整的任务何时返回?” - 当stream.ReadAsync() 返回一个时。一种真正的异步方法(进行 I/O 操作并暂停代码执行直到完成)实际上返回一个继承自 Task 的对象。最简单的例子是如果您查看the code for Task.Delay() - 它返回一个DelayPromise 对象,该对象继承自Task&lt;&gt; “为什么方法中的其余代码会在后台线程上运行完成?” - 因为这样会更快地完成(它不必等待原始线程被释放)并且没有理由不这样做。但是,在 UI 应用程序中,您只能从单个线程修改 UI,因此有理由在它开始的线程上恢复执行,这就是它在那里工作不同的原因。控制台应用程序没有这个限制。 这些链接非常有用。因此,异步方法一直运行到 return 命令返回一个名为 的承诺,并且该承诺代表不完整的 Task?

以上是关于异步程序未异步运行的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

极高的加载时间 - 请求未异步运行。猫鼬

在异步函数之外使用 await

奇怪的预言机工作行为

Lambda中的NodeJS异步未运行所有条目

Observable 的异步行为导致变量未定义

未触发异步任务