为什么在.NET Core中,Task.Wait()与Console.WriteLine()的行为不符合预期?

4

我想更好地了解异步编程的基础知识,因此我创建了以下代码片段:

private void TaskContinuations()
{
    // Task for counting prime numbers
    Task<int> primeNumberTask = Task.Run(() =>
        Enumerable.Range(2, 3000000)
        .Count(n =>
            Enumerable.Range(2, (int)Math.Sqrt(n) - 1)
            .All(i => n % i > 0)));

    var awaiter = primeNumberTask.GetAwaiter();

    // primeNumberTask.Wait(); // Option 1: Waiting but not printing (rather unexpected here)

    awaiter.OnCompleted(() => 
    {
        var result = awaiter.GetResult();
        Console.WriteLine(result);
    });

    //primeNumberTask.Wait(); // Option 2: Waiting but not printing (kind of expected here)

    Console.Read(); // Option 3: Works and prints as expected
}

我知道我需要防止主UI任务停止以避免终止后台任务。因此,Console.Read();会阻塞UI线程,并且如果在质数任务完成之前没有按键,则控制台中的输出将正确显示216816。到这里都很清楚。
因此,我认为调用Wait();会产生相同的效果(至少在OnCompleted回调之前调用),即主UI任务会阻塞并等待质数任务完成。确实会发生这种情况,但无论是选项1还是选项2,主线程都会等待然后终止而没有任何输出。
为什么会这样? 我的预期行为如下(可能有些偏差,因此我的问题)
  1. 主UI线程等待/阻塞任务直到它完成,订阅已完成的任务,因此立即执行回调,输出打印为216816。 但这并没有发生!
或者
  1. 主UI线程等待/阻塞,当任务完成时,在执行OnCompleted之前立即完成。因此,这种情况下不会产生任何输出。
那么我缺少了什么? 有人能帮我解答吗?

1
Related - Sergey Kalinichenko
如果我保留Console.Read();,它在所有情况下都会打印出'216816'。给出的选项是'异或';-) - Anytoe
如果在选项2中没有Console.Read();,程序会在.OnCompleted()被触发之前退出。 - zerkms
这符合我对Option 2的期望,但Option 1呢? - Anytoe
一样的。OnCompleted回调被安排在另一个线程中运行。 - zerkms
显示剩余8条评论
1个回答

1
首先,根据 MSDN,您不应使用 OnCompleted 方法,因为

此 API 支持产品基础结构,不建议直接从您的代码中使用。

其次,OnCompleted 方法在任务完成后被调用。所以您有一个竞争条件 - 任务已完成,并且 .Net 尝试在之后执行该方法,但是您的代码中没有任何东西等待 awaiter,因此程序在打印任何内容之前退出。
在这种情况下,您应该使用 ContinueWith 任务 并等待第二个任务:
private void TaskContinuations()
{
    // Task for counting prime numbers
    var primeNumberTask = Task.Run(() =>
        Enumerable.Range(2, 3000000)
        .Count(n =>
            Enumerable.Range(2, (int)Math.Sqrt(n) - 1)
            .All(i => n % i > 0)));

    // continuation
    var awaiterTask = primeNumberTask.ContinueWith(t => 
    {
        var result = t.Result;
        Console.WriteLine(result);
    });

    // wait for everything to execute
    awaiterTask.Wait();
}

请注意,ContinueWith有它自己的缺陷,应谨慎使用。关于续集技术的另一篇文章可以在这里找到。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接