使用 `Task.Run` 或只调用异步方法而不使用 `await`

NvM*_*Mat 4 c#

注意:这是一个针对Asp.NetWeb 应用程序领域之外的问题。

一般来说,特别是在涉及库或控制台应用程序时,为了触发并忘记异步方法,只调用异步方法而不await使用它或使用它更好Task.Run吗?

基本上:

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

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

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

Run Code Online (Sandbox Code Playgroud)

Ale*_*lex 7

抛开“我是否应该使用即发即弃”的争论和“哪些任务适合即发即弃”的争论:

就我个人而言,我使用 Gerald Barre 的这个好帮手,在 Nick Chapsas 视频博客推荐 - 原文在这里:https ://www.meziantou.net/fire-and-forget-a-task-in-dotnet.htm

有趣的事实:ASP.NET Core SignalR 使用此代码的变体。

public static class TaskExtensions
{
    /// <summary>
    /// Observes the task to avoid the UnobservedTaskException event to be raised.
    /// </summary>
    public static void Forget(this Task task)
    {
        // note: this code is inspired by a tweet from Ben Adams: https://twitter.com/ben_a_adams/status/1045060828700037125
        // Only care about tasks that may fault (not completed) or are faulted,
        // so fast-path for SuccessfullyCompleted and Canceled tasks.
        if (!task.IsCompleted || task.IsFaulted)
        {
            // use "_" (Discard operation) to remove the warning IDE0058: Because this call is not awaited, execution of the current method continues before the call is completed
            // https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards?WT.mc_id=DT-MVP-5003978#a-standalone-discard
            _ = ForgetAwaited(task);
        }

        // Allocate the async/await state machine only when needed for performance reasons.
        // More info about the state machine: https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/?WT.mc_id=DT-MVP-5003978
        async static Task ForgetAwaited(Task task)
        {
            try
            {
                // No need to resume on the original SynchronizationContext, so use ConfigureAwait(false)
                await task.ConfigureAwait(false);
            }
            catch
            {
                // Nothing to do here
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

LogRunningTask().Forget()
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢您的高质量答案和链接。问候。 (2认同)

Ste*_*ary 5

一般来说,特别是在涉及库或控制台应用程序时,为了触发并忘记异步方法,是只调用异步方法而不等待它还是使用 Task.Run 更好?

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

“火而忘记”的意思是:

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

简而言之,“即发即忘”只适用于极少数的任务。例如,更新缓存。我说大概85%以上的“发射后不管”的代码是错误的-它用火和忘记的代码应该不会是火灾和忘记。

所以我想说最好的解决方案是不使用火而完全忘记。在最起码,你应该露出Task一个表示“后续行动”的地方。考虑将 添加Task到您的返回类型或将其作为属性公开。

采用即发即弃——尤其是在图书馆中——意味着你迫使所有消费者永远不知道何时关闭和退出是安全的。但是,如果您真的想开火而忘,则有几种选择。

A. 一种选择是在没有上下文的情况下调用async void函数。消费应用程序仍然无法确定代码是否/何时完成,但至少不会忽略异常。

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

这两个建议都是在没有上下文的情况下开始任务的。有帮助程序可以执行此操作,或者您可以将调用包装起来Task.Run(效率稍低,但效果很好)。

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