为什么控制台没有任何输出?

5

我创建了两个打印x和y各100次的方法。我想让它们并发运行,我期望输出结果为xxxxyxyxyyyxyyxyx...等等。 但是代码没有打印任何内容。我是否漏掉了一些逻辑?

using System;
using System.Threading.Tasks;

namespace ConsoleApplication32
{
    internal class Program
    {
        public static async Task<int> Print1()
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.Write("x");
                }
            });

            return 1;
        }

        public static async Task<int> Print2()
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.Write("y");
                }
            });

            return 1;
        }

        public static void Run()
        {
            Task<int> i = Print1();
            Task<int> k = Print2();
        }

        private static void Main(string[] args)
        {
            Run();
        }
    }
}

为什么不直接附加调试器来查看? - Liam
Program 在两个子线程中启动了两个任务,然后立即结束,结束了所有的子线程,因此两个 Print 都没有运行。请等待这些任务结束。 - Marco Scabbiolo
3个回答

12

解释

我们需要进行一些调整才能得到所需的输出:xxxxyxyxyyyxyyxyx

  1. Run方法未等待Task iTask k完成

    • 这意味着控制台应用程序会在两个Task完成之前关闭
    • Andrei Matracaru在他的答案中所提到的,如果添加Console.ReadLine(),它将防止控制台应用程序在字符串被打印到屏幕上之前关闭。
    • 一种更优雅的解决方案是使用Task.WhenAllTask.WhenAll将允许代码向其调用线程(在本例中为主线程)产生效果,这不会冻结应用程序的UI,并允许应用程序继续打印到屏幕上,直到Task完成。对于简单的控制台应用程序来说,这似乎微不足道,但对于构建任何用户交互应用程序,如移动或Web应用程序,从不锁定主线程至关重要;锁定主线程会导致应用程序“冻结”。
    • Camilo Terevinto上面的答案建议使用Task.WaitAll。这也是不好的做法,因为它也会冻结应用程序;Task.WaitAll不可等待并且不能产生效果到调用线程。
  2. Print1()Print2()从不产生效果到主线程

    • 这些方法在后台线程上执行,但它们在for循环内部没有包含await
    • for循环内部需要一个await来允许CPU继续执行主线程,因为Console.Write()只能在主线程上打印到屏幕上。

代码

using System;
using System.Threading.Tasks;

namespace ConsoleApplication32
{
    internal class Program
    {
        public static async Task<int> Print1()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.Write("x");
                await Task.Delay(10);
            }

            return 1;
        }

        public static async Task<int> Print2()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.Write("y");
                await Task.Delay(10);
            }

            return 1;
        }

        public static async Task Run()
        {
            var i = Print1();
            var k = Print2();

            await Task.WhenAll(i, k);
        }

        private static void Main(string[] args)
        {
            Task.Run(async () => await Run()).GetAwaiter().GetResult();
        }
    }
}

输出

xyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxxyxyxyxy 按任意键继续...

C# 7.1的替代方案

C# 7.1引入了具有async Main方法的能力。这意味着我们可以将Main方法调整如下:

    ...

    private static async Task Main(string[] args)
    {
        await Run();
    }
    ...

2
很惊奇你能从我的评论中得出使用Task.Delay的答案并获得更多的赞... Task.Run(async () => await Run()).Wait();是一个双重包装,你可以直接使用Run().Wait()。你还没有提到只有Visual Studio 2017 Preview 3才能运行C# 7.1代码。 - Camilo Terevinto
5
我建议使用.GetAwaiter().GetResult()代替.Wait()或者.Result,因为它会提供更好的堆栈跟踪信息来帮助定位异常。请注意,不要改变原意。 - Cheesebaron
2
有关控制台应用程序和线程亲和性的额外奖励材料:https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/ - Krumelur

5
我尝试了您的代码,运行良好。可能您认为它不起作用,是因为控制台窗口关闭得非常快。请在主函数中添加 Console.ReadLine()
private static void Main(string[] args)
{
    Run();
    Console.ReadLine();
}

3
问题在于你没有等待任务完成,因此程序终止(没有同步代码阻塞)。如果你想让任务并行运行,请使用以下方法:
public static void Run()
{
    Task<int> i = Print1();
    Task<int> k = Print2();
    Task.WaitAll(i, k);
}

另一种等待任务完成的方法是这样的:
public static async Task Run()
{
    Task<int> i = Print1();
    Task<int> k = Print2();
    await Task.WhenAll(i, k);
}

public static void Main(string[] args)
{
    Run().GetAwaiter().GetResult();
}

我尝试了你说的方法,但这次方法是按顺序处理的。输出结果为:xxxxxxxx(100次)yyyyyyy(100次)。我需要输出结果像这样:xxxxyyxyxyxyxyxyyyx... - Lyrk
不,他们没有 @Lyrk。它们只是按顺序运行而已。你的任务并没有做很多事情,这意味着它们将非常快地写入,这意味着第一个任务可能会在第二个任务甚至初始化之前就完成。它们仍然在独立的线程上运行。 - Liam
3
为了支持@Liam的说法,你可以在Print1循环内添加await Task.Delay(10); - Camilo Terevinto
2
说实话,这些都是无意义的(我猜你只是在测试任务)。在现实生活中,你永远不会这样做。这可能是将内容写入控制台的最低效方式。 - Liam
@Lyrk 这取决于你是否添加了任何使程序等待的内容,就像Andrei Matracaru在他的回答中建议的那样。在这里使用Task.WaitAll(以我写的方式使用)将会产生同步、阻塞的等待,这样程序在等待期间不会继续执行。 - Camilo Terevinto
显示剩余5条评论

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