为什么 Main 不等待所有线程在异步程序中求值?

Why does Main not wait for all threads to evaluate in asynchronous program?

@Dan Dinu 在之前关于 C# 异步编程的问题中的回答提供了一个有用的最小示例,我将其改编如下:

// From 

using System;
using System.Threading.Tasks;

namespace minimal_async_await_SE
{
    internal class Program
    {
        public static async Task MyMethodAsync()
        {
            Task<int> longRunningTask = LongRunningOperationAsync();
            // independent work which doesn't need the result of LongRunningOperationAsync
            // can be done here
            Console.WriteLine("Independent work");
            //Call await on the task
            int result = await longRunningTask;
            Console.WriteLine(result);
        }

        public static async Task<int> LongRunningOperationAsync()
        {
            await Task.Delay(1000);
            return 1;

        }

        static void Main(string[] args)
        {
            MyMethodAsync();
            Console.WriteLine("Returned to Main");
            //Console.ReadKey();
        }
    }
}

如果我取消注释第 32 行,我会得到以下预期结果:

Independent work
Returned to Main
1

基本上:

  1. Main 呼叫 MyMethodAsync
  2. MyMethodAsync 呼叫 LongRunningOperationAsync
  3. LongRunningOperationAsync 然后调用 Task.Delay,但是 await 它暂停了对封闭方法 LongRunningOperationAsync 的进一步评估,将控制权返回给调用者,即 MyMethodAsync.
  4. MyMethodAsync 打印出 "Independent work".
  5. MyMethodAsync 尝试分配 LongRunningOperation to resultbutawaits it, suspends evaluation of the enclosing MyMethodAsync, and returns control of the program back to Main`
  6. 的结果
  7. Main 打印出 "Returned to Main"
  8. Task.Delay(1000)LongRunningOperationAsync() 完成
  9. 产生了一个新线程,在 LongRunningOperationAsync (MyMethodAsync) 的调用者中,整数 1 被分配给 result.
  10. MyMethodAsync 的计算完成,MyMethodAsync 打印出 result
  11. 的值
  12. 控制权交还给 Main,这会暂停计算,直到用户通过 Console.ReadKey
  13. 输入密钥

首先,我对这个程序如何求值的理解正确吗?其次,为什么我在评论Console.ReadKey的时候,会得到如下意想不到的结果?

Independent work
Returned to Main

Main 方法是否在退出程序之前不等待所有线程都得到评估?为什么或为什么不?

“为什么或为什么不?”问题的答案。很复杂,但可以通过更好地理解 Task.

来回答

A Task 不是线程。许多任务可以 运行 在单个线程上,单个任务可以 运行 在多个线程上。

任务更像是一个事件,它会触发调度程序 运行 在当时可用的任何线程上执行一些代码(忽略一些复杂的延续问题)

所以您的问题可能是re-phrased“为什么我的程序不监听所有事件并在所有事件都触发之前阻止执行?”。这个问题的答案可能让 1 或 2 个 TPL(任务)的设计者彻夜难眠,最终他们认为这个决定的影响有可能对其他类型的应用程序造成一些严重的伤害

TPL 的设计者确实为我们提供了解决此问题的方法(后来的几个 C# 版本),即异步 Main 方法。在您的情况下,它看起来像这样:

    static async Task Main(string[] args)
    {
        await MyMethodAsync();
        Console.WriteLine("Returned to Main");
    }