【问题标题】:What does calling a Task without Async/Await directly actually do? [closed]直接调用没有 Async/Await 的任务实际上做了什么? [关闭]
【发布时间】:2021-08-28 20:29:32
【问题描述】:

以应用程序调用 DoThis() 函数的简单示例为例:

        public Task DoThis()
        {
            DoThisSub(); //Why is this being run?
            Task.Delay(100); //Yet..... Why is this ignored?
            Console.WriteLine("This is run immediately without delay");
            return Task.CompletedTask;
        }

        public Task DoThisSub()
        {
            Console.WriteLine("Yes I am being run!");
            return Task.CompletedTask;
        }

为什么调用 DoThisSub 而不是调用 Task.Delay

【问题讨论】:

  • 你怎么会被忽略?
  • await 基本上意味着“等待任务完成”。不使用await 意味着您无需等待任务完成。 Task.Delay 被忽略是什么意思?你怎么知道它被忽略了?你期望会发生什么? Task.Delay(100) 给你一个持续 100 毫秒的任务,但你没有等待它。
  • 首先,Task.Delay 不会被忽略。您正在做的是说启动一个 100 毫秒计时器。它返回一个任务,您可以await 或使用它来确定它是否已完成。但是你忽略它,所以,计时器开始,它最终会结束,但你不在乎。这就是调用任务而不等待它或做任何其他事情所做的事情,它只是启动它并忘记它。它经常被称为fire and forget
  • 它不一定在单独的线程上运行。不要忘记Task != Thread。操作系统很可能能够发出计时器结束的信号,而无需专门为其分配线程。如果您从端口异步读取,则不涉及线程。当您在 EF 查询的结果上调用 ToListAsync 时,它会从数据库中异步读取,但不会专门为工作分配线程。 Windows 中的很多东西都是自然异步的
  • 您可能会发现这些很有趣:Task vs Thread differencesThere is no thread

标签: c# .net async-await


【解决方案1】:

为什么调用了 DoThisSub 而没有调用 Task.Delay?

他们都被称为。它们都返回任务实例,它们都被忽略了。

这里没有“单独的线程”;所有方法都在同一个线程上运行。

【讨论】:

    【解决方案2】:

    Task.Delay 方法创建了一个新任务,该任务将在一段时间后完成,因此不会被忽略。 (参考:https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay?view=net-5.0

    它正在被调用并且另一个线程可能会运行延迟(要确切知道哪个线程将运行任务取决于许多因素),但是因为调用线程没有等待 Task.Delay方法它会立即继续处理下一行。

    如果您希望调用线程等待 Task.Delay 方法,有几个选项:

    1. 使 DoThis 方法异步并等待 Task.Delay 方法。

      public async Task DoThis()
      {
          DoThisSub();
          await Task.Delay(100);
          Console.WriteLine("This will not be called immediately after the Task.Delay(100)");
          return Task.CompletedTask;
      }
      
    2. Task.Delay()

      之后调用Wait()方法
      public Task DoThis()
      {
          DoThisSub();
          Task.Delay(100).Wait();
          Console.WriteLine("This will not be called immediately after the Task.Delay(100)");
          return Task.CompletedTask;
      }
      

    我强烈建议您阅读 microsoft async/await 文档。 (参考:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

    解决 cmets 中的问题:

    为什么 Task.Delay 在单独的线程上运行,而 DoThisSub() 不是

    DoThisSub() 方法只是一个返回任务的常规方法。并非所有返回 Task 的方法实际上都在新线程上运行。为此,该方法需要在新任务上实际运行代码,如下例所示:

        public Task DoThisSub()
        {
            return Task.Run(() => 
            {
                Console.WriteLine("Yes I am being run on a new thread!");
            });
        }
    

    【讨论】:

    • 如果这听起来很粗鲁,我很抱歉。这不是本意。我刚刚编辑了答案以解决此评论。你现在明白了么?我真的建议您阅读文档,因为几年前我问过自己同样的问题,而我真正理解发生了什么的唯一方法就是阅读它。
    • “另一个线程正在执行延迟”“Task.Delay 方法在单独的线程上运行” Task.Delay(10000) 任务并将它们存储在List<Task> 中会发生什么?是否会有 1,000,000 个单独的线程运行/执行任务?如果我在 Windows 任务管理器中查看我的程序,我会看到这么多线程吗?
    • 谢谢布鲁诺,我没有被冒犯。很有趣的是,很多程序员(包括我自己)在没有正确理解 async-await 的底层工作原理的情况下能够存活这么多年。你的答案是最好的。谢谢
    • 很好的问题,西奥多。我相信您将无法在 Windows 任务管理器中看到它,因为您的计算机可能没有那么多线程。 Tasks 的工作方式是将工作实际委派给线程池中可用的线程。
    • 范式的部分想法是我们不必一直理解它到寄存器。 :)
    【解决方案3】:

    被忽略了!它在单独的线程上调用,并且由于您不是 awaiting 结果控制立即移动到下一条语句

    【讨论】:

    • 谢谢。但为什么要在单独的线程上调用它?我没有等待 if 也没有执行 Task.Run 。
    • 因为这是您要求它做的事情,所以不等待它,它必须在不同的线程中运行。
    • Chris Schaller.. 谢谢,但你的说法是错误的...... DoThisSub();不在单独的线程上运行...它在同一个线程上运行!
    • 是什么让你这么想?您的测试应该尝试打印出任务中的线程 ID。当您不使用awaitTask.Run 时,您无法创建回调或至少控制编组,更重要的是您无法捕获任务的异常或结果。该任务留给当前调度程序上下文来管理,可能最终会在同一个线程中,但 when 是不确定的
    • @ChrisSchaller Task.Delay 不会在另一个线程上运行,因为它根本没有运行任何东西。它只是创建一个将在指定时间后完成的任务,并且在此期间什么都不做
    【解决方案4】:

    Task 和 Thread 之间没有直接关系。 Task-await-async 只是 Asynchronous-task + EventHandler 的简化形式。 (相关概念:EventsDelegatesLambda expressions)。

    例如,虽然我们现在很随意地使用 flat code 如下,

    async {
        Debug.WriteLine("Before");
        await Task.Delay(100);
    
        Debug.WriteLine("After");
        :
        Debug.WriteLine("The End");
    }
    

    曾几何时(可能在 .NET3.x 或早期 .NET4.x 时代,请参阅 C# Version History),没有等待异步语法。只有 AsynchronousDelayTaskWorker 类或其他东西(1)接受事件处理程序,(2)异步运行耗时的任务,然后(3)最后触发事件。为了使用这种设备,程序员必须编写如下复杂的结构化代码。

    {
       Debug.WriteLine("Before");
       var worker = new AsynchronousDelayTaskWorker();
       worker.SetCompletedEventHandler(new CompletedEventHander(()=>
       {
           // Whether this handler is dispatched on the original thread or not
           // depends on worker's implementation. 
    
           Debug.WriteLine("After");
           :
           Debug.WriteLine("The End");
       }));
       worker.RunAsync();
    }
    

    但是在task-async-await were introduced in .NET4.5之后,这样的代码就可以写成如下这样一个简洁干净的方式了。

    async {
    
        Debug.WriteLine("Before");
        await Task.Delay(100);
    
        // Following codes are actually placed in event handler.
        Debug.WriteLine("After");
        :
        Debug.WriteLine("The End");
    }
    

    ——C#程序员从此过上了幸福的生活。

    使用不带等待的任务几乎等同于在不注册事件处理程序的情况下运行旧的异步任务。该任务在某处异步运行(在许多情况下在后台线程中)。 任务完成后无法运行另一个代码,因为不知道任务何时完成/完成,没有事件处理程序。

    由于任务只是对象,不干扰后续代码,所有的行都直接运行。

    {
        Debug.WriteLine("Before");
    
        // The following worker runs somewhere 
        // as the raw Task.Delay(100) does.
        var worker = new AsynchronousDelayTaskWorker();
        worker.RunAsync(); 
    
        // Following codes run immediately.
        Debug.WriteLine("After"); 
        :
        Debug.WriteLine("The End");
    }
    

    【讨论】:

      猜你喜欢
      • 2015-03-21
      • 2014-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-02
      • 2020-11-21
      • 2015-02-26
      • 2017-06-29
      相关资源
      最近更新 更多