【问题标题】: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 的文章是必读的。

标签: c# multithreading asynchronous async-await threadpool


【解决方案1】:

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

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

  1. Main 一直运行到它调用 ProcessReadWriteAsync(@"/tmp/temp1Write.txt")
  2. ProcessReadWriteAsync(string) 一直运行到它调用 ReadWriteAsync(filePath)
  3. ReadWriteAsync(string) 一直运行到它调用 ReadWriteAsync(path, "")
  4. ReadWriteAsync(string,string) 一直运行到它调用 stream.ReadAsync(buffer, 0, buffer.Length)
  5. stream.ReadAsync() 做了一些魔法,直到它返回一个不完整的 Task
  6. ReadWriteAsync(string,string) 中的await 看到不完整的Task 并且返回一个新的不完整Task,该方法的其余部分注册为该Task 的延续
  7. ReadWriteAsync(string) 返回一个不完整的 Task
  8. ProcessReadWriteAsync(string) 返回一个不完整的 Task
  9. Main() 中还没有await,所以在那里继续执行。
  10. 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?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-17
  • 2015-12-29
  • 1970-01-01
  • 2017-05-13
  • 2017-09-13
  • 2018-07-18
相关资源
最近更新 更多