异步程序未异步运行的奇怪行为
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】:
所有异步方法都开始在当前线程上运行。在您点击作用于不完整的Task
的await
之前,不会发生任何不同。但是无论你向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
设置为Completed
,ReadWriteAsync(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<>
。
“为什么方法中的其余代码会在后台线程上运行完成?” - 因为这样会更快地完成(它不必等待原始线程被释放)并且没有理由不这样做。但是,在 UI 应用程序中,您只能从单个线程修改 UI,因此有理由在它开始的线程上恢复执行,这就是它在那里工作不同的原因。控制台应用程序没有这个限制。
这些链接非常有用。因此,异步方法一直运行到 return 命令返回一个名为 的承诺,并且该承诺代表不完整的 Task
?以上是关于异步程序未异步运行的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章