72

我正在尝试用新的语法替换我旧的即发即弃的调用,希望更简单,它似乎在逃避我。这是一个例子

class Program
{
    static void DoIt(string entry) 
    { 
        Console.WriteLine("Message: " + entry);
    }

    static async void DoIt2(string entry)
    {
        await Task.Yield();
        Console.WriteLine("Message2: " + entry);
    }

    static void Main(string[] args)
    {
        // old way
        Action<string> async = DoIt;
        async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
        Console.WriteLine("old-way main thread invoker finished");
        // new way
        DoIt2("Test2");   
        Console.WriteLine("new-way main thread invoker finished");
        Console.ReadLine();
    }
}

两种方法都做同样的事情,但是我似乎已经获得了(无需EndInvoke关闭句柄,这仍然有点值得商榷)我不得不等待 a 以新的方式失败Task.Yield(),这实际上带来了一个新的问题必须重写所有现有的异步 F&F 方法只是为了添加那个单行。在性能/清理方面是否有一些看不见的收益?

如果我无法修改后台方法,我将如何应用异步?在我看来,没有直接的方法,我必须创建一个等待 Task.Run() 的包装异步方法?

编辑:我现在看到我可能错过了一个真正的问题。问题是:给定一个同步方法 A(),我怎样才能使用async/await以一种即发即弃的方式异步调用它,而不会得到比“旧方法”更复杂的解决方案

4

5 回答 5

113

避免async void。它在错误处理方面具有棘手的语义;我知道有些人称它为“fire and forget”,但我通常使用“fire and crash”这个短语。

问题是:给定一个同步方法 A(),我怎样才能以一种即发即弃的方式使用 async/await 异步调用它,而不会得到比“旧方法”更复杂的解决方案

你不需要async/ await。就这样称呼它:

Task.Run(A);
于 2012-10-09T16:06:02.890 回答
68

正如其他答案中所述,并且通过这篇出色的博客文章,您希望避免使用async voidUI 事件处理程序之外的内容。如果您想要一种安全的“即发即弃”async方法,请考虑使用此模式(感谢@ReedCopsey;此方法是他在聊天对话中给我的):

  1. Task. 它运行传递Task并捕获/记录任何异常:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. 创建样式时始终使用Task样式方法,从不.asyncasync void

  3. 以这种方式调用这些方法:

    MyTaskAsyncMethod().FireAndForget();
    

你不需要await它(它也不会产生await警告)。它还将正确处理任何错误,并且由于这是您放置的唯一位置async void,因此您不必记住在try/catch任何地方放置块。

如果您实际上想要正常使用,这也为您提供了将该方法用作“即发即弃”方法的选项。asyncawait

于 2015-01-09T01:19:52.560 回答
22

在我看来,“等待”某事和“一劳永逸”是两个正交的概念。您要么异步启动一个方法并且不关心结果,要么您希望在操作完成后继续在原始上下文上执行(并且可能使用返回值),这正是 await 所做的。如果您只想在 ThreadPool 线程上执行一个方法(这样您的 UI 不会被阻塞),请选择

Task.Factory.StartNew(() => DoIt2("Test2"))

你会没事的。

于 2012-10-09T15:26:47.467 回答
1

我的感觉是,这些“一劳永逸”的方法在很大程度上是需要一种干净的方式来交错 UI 和后台代码的人工制品,以便您仍然可以将逻辑编写为一系列顺序指令。由于 async/await 负责通过 SynchronizationContext 进行编组,因此这不再是一个问题。较长序列中的内联代码有效地成为您的“即发即弃”块,这些块以前会从后台线程中的例程中启动。它实际上是模式的反转。

主要区别在于等待之间的块更类似于 Invoke 而不是 BeginInvoke。如果您需要更像 BeginInvoke 的行为,您可以调用下一个异步方法(返回一个任务),然后在您想要“BeginInvoke”的代码之后才真正等待返回的任务。

    public async void Method()
    {
        //Do UI stuff
        await SomeTaskAsync();
        //Do more UI stuff (as if called via Invoke from a thread)
        var nextTask = NextTaskAsync();
        //Do UI stuff while task is running (as if called via BeginInvoke from a thread)
        await nextTask;
    }
于 2012-10-09T15:16:08.117 回答
1

这是我根据 Ben Adams 关于构建这种结构的推文整理的课程。HTH https://twitter.com/ben_a_adams/status/1045060828700037125

using Microsoft.Extensions.Logging;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

// ReSharper disable CheckNamespace
namespace System.Threading.Tasks
{
    public static class TaskExtensions
    {
        [SuppressMessage("ReSharper", "VariableHidesOuterVariable", Justification = "Pass params explicitly to async local function or it will allocate to pass them")]
        public static void Forget(this Task task, ILogger logger = null, [CallerMemberName] string callingMethodName = "")
        {
            if (task == null) throw new ArgumentNullException(nameof(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
            // Pass params explicitly to async local function or it will allocate to pass them
            static async Task ForgetAwaited(Task task, ILogger logger = null, string callingMethodName = "")
            {
                try
                {
                    await task;
                }
                catch (TaskCanceledException tce)
                {
                    // log a message if we were given a logger to use
                    logger?.LogError(tce, $"Fire and forget task was canceled for calling method: {callingMethodName}");
                }
                catch (Exception e)
                {
                    // log a message if we were given a logger to use
                    logger?.LogError(e, $"Fire and forget task failed for calling method: {callingMethodName}");
                }
            }

            // 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.IsCanceled && (!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://docs.microsoft.com/en-us/dotnet/csharp/discards#a-standalone-discard
                _ = ForgetAwaited(task, logger, callingMethodName);
            }
        }
    }
}
于 2021-06-07T18:45:56.123 回答