睡眠真的会阻止执行吗?

Is Sleep really blocking the execution?

几乎所有关于 C# 异步编程的介绍都警告不要使用 Sleep 指令,因为它会阻塞整个线程。

但是我发现在睡眠期间,正在从队列中获取并执行任务。参见:

    using System;
    using System.Threading.Tasks;

    namespace TestApp {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Main");
                Program.step1();

                for (int i = 0; i < 6; i++) {
                    System.Threading.Thread.Sleep(200);
                    Console.WriteLine("Sleep-Loop");
                }
            }

            private static async void step1() {
                await Task.Delay(400);
                Console.WriteLine("Step1");
                Program.step2();
            }

            private static async void step2() {
                await Task.Delay(400);
                Console.WriteLine("Step2");
            }
        }
    }

输出:

    Main
    Sleep-Loop
    Sleep-Loop
    Step1
    Sleep-Loop
    Sleep-Loop
    Step2
    Sleep-Loop
    Sleep-Loop

我的问题:

  1. Sleep 真的允许排队的任务执行,还是发生了其他事情?
  2. 如果是,那么其他所有闲置情况是否也会发生这种情况?例如在轮询期间?
  3. 在上面的例子中,如果我们注释掉循环,那么应用程序会在任何任务执行之前退出。还有其他方法可以防止这种情况发生吗?

在 C# 7.3 中,您可以拥有异步入口点,我建议使用它。

一些注意事项:

  1. 不要使用 async void,它在处理错误的方式上有微妙之处,如果你看到自己在写 async void 然后想想你在做什么。如果不是事件处理程序,你可能做错了什么
  2. 如果你想等待一堆任务完成,使用Task.WhenAll

修改示例

static async Task Main(string[] args)
{
   Console.WriteLine("Start Task");
   var task = Program.step1();

   for (int i = 0; i < 6; i++)
   {
      await Task.Delay(100);
      Console.WriteLine("Sleep-Loop");
   }

   Console.WriteLine("waiting for the task to finish");
   await task;
   Console.WriteLine("finished");
   Console.ReadKey();
}

private static async Task step1()
{
   await Task.Delay(1000);
   Console.WriteLine("Step1");
   await Program.step2();
}

private static async Task step2()
{
   await Task.Delay(1000);
   Console.WriteLine("Step2");
}

重要的是要注意任务不是线程,async 不是并行的,但它们可以是。

如果您使用异步等待模式,那么 10 次中有 9 次是为了 IO 绑定工作使用操作系统 I/O 完成端口,这样您就可以释放线程。这是一种可扩展性和 UI 响应能力。

如果您没有做任何 I/O 工作,那么实际上根本不需要 async await 模式,因此 CPU工作可能应该在调用时被包裹在 Task.Run 中。未包含在 async 方法中。

此时还需要注意的是,仅使用任务不是异步和等待模式。虽然他们都有共同的任务,但他们不是一回事。

最后,如果您发现需要以一种即用即忘的方式使用异步代码,请仔细考虑您将如何处理任何错误。

这里有一些指南。

  • 如果您想做 I/O 工作,请使用异步等待模式。
  • 如果您想做 CPU 工作,请使用 Task.Run。
  • 切勿使用 async void 除非它用于事件处理程序。
  • 永远不要在异步方法中包装 CPU 工作,让调用者使用 Task.Run
  • 如果你需要等待任务,await它,永远不要调用Result,或Wait或使用Task.WhenAll