ContinueWith 方法返回的任务不需要 Wait() 方法?

Wait() method not needed for the task returned by the ContinueWith method?

为什么即使我不对方法 ContinueWith 返回的第二个任务使用 Wait() 方法,代码仍然有效?

如果我不对第一个任务使用 Wait() 方法,那么什么都不会起作用,但对第二个任务无效。

using System;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            void MyMethod1()
            {
                Console.WriteLine(1);
            }

            void MyMethod2(Task MyTask)
            {
                Console.WriteLine(2);
            }

            Task MyTask1 = new Task(MyMethod1);

            MyTask1.Start();
            MyTask1.Wait();

            Task MyTask2 = MyTask1.ContinueWith(MyMethod2); //Why it is work?
            //MyTask2.Wait();
        }
    }
}

发生这种情况是因为在后台任务将输出写入控制台之前控制台应用程序正在退出。

如果您将 Console.ReadLine() 添加到该方法的末尾并尝试 运行 它而没有任何 .Wait() 您会看到它输出 12 符合预期。

那么,为什么会这样呢?

嗯,这是因为有与第一次写入控制台相关的开销 - 后续写入不会发生的开销。

所以当你没有 Wait() 时,第一个 Console.WriteLine() 在程序退出之前没有时间完成。

但是,当您将 Wait() 添加到第一个任务时,第一个 Console.WriteLine() 已经(显然)完成并且第二个 Console.WriteLine() 没有与第一个,它有时间在程序退出前完成。

(这是一个竞争条件,实际结果可能因不同的运行和不同的环境而异。)

我们可以执行一些基本的计时来显示第一次调用 Console.WriteLine() 和第二次调用之间的时间差。 (注意:通常我使用 Bench.Net 来计时,但对于这种特殊情况,这不是很好。)

static void Main()
{
    Stopwatch sw = Stopwatch.StartNew();
    Console.WriteLine("1");
    sw.Stop();
    Console.WriteLine("First Console.WriteLine() took " + sw.Elapsed);

    sw.Restart();
    Console.WriteLine("2");
    sw.Stop();
    Console.WriteLine("Second Console.WriteLine() took " + sw.Elapsed);
}

对于我的 PC 上的发布版本,输出:

1
First Console.WriteLine() took 00:00:00.0060951
2
Second Console.WriteLine() took 00:00:00.0000624

如您所见,第二次调用比第一次调用快了将近 100 倍。如果程序在 0.0060951 秒内退出,将看不到任何输出。

但是如果 Console.WriteLine() 之前已经执行过,程序将不得不在 0.0000624 秒内退出以使输出不出现(大约!)。

您还可以通过在程序开头添加一个 Console.WriteLine() 来证明这一点,以便后续调用避免第一次调用的开销:

Console.WriteLine("Priming");

void MyMethod1()
{
    Console.WriteLine(1);
}

void MyMethod2(Task MyTask)
{
    Console.WriteLine(2);
}

Task MyTask1 = new Task(MyMethod1);
MyTask1.Start();
Task MyTask2 = MyTask1.ContinueWith(MyMethod2); //Why it is work?

如果你这样做,输出是 12 - 即使没有调用 MyTask1.Wait();.

再次注意,这是一个竞争条件,实际结果可能会有所不同。它依赖于对 WriteLine() 的调用在程序实际退出之前完成。

你从这里得到的结论应该是多线程是棘手的,你必须正确地做到这一点,否则会发生这样奇怪的事情!