【问题标题】:Fire and forget, using `Task.Run` or just calling an async method without `await`使用 `Task.Run` 或仅调用不带 `await` 的异步方法时触发并忘记
【发布时间】:2020-07-01 20:12:45
【问题描述】:

注意:这是针对Asp.Net 和网络应用领域之外的问题。

一般来说,尤其是对于库或控制台应用程序,为了触发和忘记异步方法,是直接调用异步方法而不使用awaiting 还是使用Task.Run

基本上:

public static void Main(){
  // Doing
  _ = DoSomethingAsync();

  // vs.
  _ = Task.Run(DoSomethingAsync);
}

private static async Task DoSomethingAsync()
 {
   ...
 }

【问题讨论】:

    标签: c#


    【解决方案1】:

    一般来说,尤其是对于库或控制台应用程序,为了触发和忘记异步方法,是直接调用异步方法而不等待它还是使用 Task.Run 更好?

    一般来说,最好不要使用即发即弃。

    “一劳永逸”的意思是:

    1. 你不在乎代码是否有异常。任何异常都会导致它静默失败;没有日志记录、没有通知等。
    2. 您不需要知道代码何时完成。即,消费应用程序不需要等待代码完成。即使在关机期间。
    3. 您不需要完成代码。作为 (2) 的推论,即发即弃的代码可能无法运行完成;并且作为 (1) 的推论,您将不会收到未完成的通知。

    简而言之,“一劳永逸”只适用于极少数的任务。例如,更新缓存。我会说可能 85% 或更多的“即发即弃”代码是错误 - 它使用即发即弃的代码应该是即发即弃。 p>

    所以我想说最好的解决办法是不使用火而完全忘记。 至少,您应该在某处公开一个Task,以表示“后续行动”。考虑将Task 添加到您的返回类型或将其作为属性公开。

    采用“一劳永逸”(尤其是在图书馆中)意味着您迫使所有消费者永远不知道何时可以安全地关闭和退出。但如果你真的想一劳永逸,有几个选择。

    A.一种选择是调用async void 函数without a context。消费应用程序仍然无法确定代码是否/何时完成,但至少不会忽略异常。

    B.另一种选择是在没有上下文的情况下启动任务。此选项同时具有触发代码和忘记代码的缺点:忽略异常并且调用代码不知道何时完成。

    这两个建议都在没有上下文的情况下启动任务。有helpers for doing this,或者你可以把调用封装在Task.Run中(效率稍低,但效果很好)。

    我不建议直接启动任务。虽然这在控制台应用程序中可以正常工作,但不适用于可能在提供上下文的情况下调用的库。

    【讨论】:

      【解决方案2】:

      一般来说,这取决于但Task.Run 是更安全的选择,其背后的原因取决于两件事:

      1. 如果我们能保证async 方法总是异步的。对于库项目或异步方法位于不受调用者管理的代码中,保证方法始终是异步的可能会变得特别棘手。例如,一旦我在库中实现了类似于此示例的方法:
       public async Task HandleMessages(Func<Task> onMessageNotification)
       {
         while(true)
         {
            var msg = MessagingClient.ReceiveMessage();
            ...
           if(msg.MessageType == "MakeAPizza")
           { 
             _ = onMessageNotification();
             _ = MakePizzaAsync();
           }
         }
       }
      
       private async Task MakePizzaAsync() {...}
      

      我想触发并忘记onMessageNotificationMakePizzaAsync,因为我不希望它阻碍消息处理。我没有注意到的是调用者实现onMessageNotification 时如下所示:

      Func<Task> onMsgNotification = () => {
        DoSlowOperation();
        return Task.CompletedTask;
      }
      

      这实际上会使onMessageNotification 同步操作,并且必须等待DoSlowOperation() 完成。因此,为了避免解决问题,我只需将其更改为:

        _ = Task.Run(onMessageNotification);
        _ = MakePizzaAsync();
      
      1. 在第一次await 操作之前,我们不会在异步方法中进行长时间运行的操作。调用者可能导致问题的另一种方式是,如果 onMsgnotification Func 以下列方式实现:
      Func<Task> onMsgNotification = async () => {
        DoSlowOperation();
        await AsyncOperation();
      }
      
      

      这仍然会减慢HandleMessages 方法的速度,因为DoSlowOperation 仍在同步调用。

      【讨论】:

        猜你喜欢
        • 2016-06-21
        • 2014-05-05
        • 2014-12-06
        • 2020-04-25
        • 2010-09-14
        • 2013-09-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多