C# 异步与“旧异步委托”的即发即忘

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12803012/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:31:20  来源:igfitidea点击:

Fire-and-forget with async vs "old async delegate"

c#asynchronousc#-5.0

提问by mmix

I am trying to replace my old fire-and-forget calls with a new syntax, hoping for more simplicity and it seems to be eluding me. Here's an example

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

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();
    }
}

Both approaches do the same thing, however what I seem to have gained (no need to EndInvokeand close handle, which is imho still a bit debatable) I am losing in the new way by having to await a Task.Yield(), which actually poses a new problem of having to rewrite all existing async F&F methods just to add that one-liner. Are there some invisible gains in terms of performance/cleanup?

两种方法都做同样的事情,但是我似乎得到了什么(不需要EndInvoke和关闭句柄,恕我直言仍然有点值得商榷)我不得不等待 a 以新的方式失败Task.Yield(),这实际上提出了一个新问题不得不重写所有现有的异步 F&F 方法来添加单行。在性能/清理方面是否有一些无形的收益?

How would I go about applying async if I can't modify the background method? Seems to me that there is no direct way, I would have to create a wrapper async method that would await Task.Run()?

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

Edit: I now see I might be missing a real questions. The question is: Given a synchronous method A(), how can I call it asynchronously using async/awaitin a fire-and-forget manner without getting a solution that is more complicated than the "old way"

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

采纳答案by Stephen Cleary

Avoid async void. It has tricky semantics around error handling; I know some people call it "fire and forget" but I usually use the phrase "fire and crash".

避免async void。它在错误处理方面具有棘手的语义;我知道有些人称它为“火灾和遗忘”,但我通常使用“火灾和崩溃”这个词。

The question is: Given a synchronous method A(), how can I call it asynchronously using async/await in a fire-and-forget manner without getting a solution that is more complicated than the "old way"

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

You don't need async/ await. Just call it like this:

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

Task.Run(A);

回答by Dan Bryant

My sense is that these 'fire and forget' methods were largely artifacts of needing a clean way to interleave UI and background code so that you can still write your logic as a series of sequential instructions. Since async/await takes care of marshalling through the SynchronizationContext, this becomes less of an issue. The inline code in a longer sequence effectively becomes your 'fire and forget' blocks that would previously have been launched from a routine in a background thread. It's effectively an inversion of the pattern.

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

The main difference is that the blocks between awaits are more akin to Invoke than BeginInvoke. If you need behavior more like BeginInvoke, you can call the next asynchronous method (returning a Task), then don't actually await the returned Task until after the code that you wanted to 'BeginInvoke'.

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

    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;
    }

回答by Daniel C. Weber

To me it seems that "awaiting" something and "fire and forget" are two orthogonal concepts. You either start a method asynchronously and don't care for the result, or you want to resume executing on the original context after the operation has finished (and possibly use a return value), which is exactly what await does. If you just want to execute a method on a ThreadPool thread (so that your UI doesn't get blocked), go for

对我来说,“等待”和“火后忘记”似乎是两个正交的概念。您要么异步启动一个方法并且不关心结果,要么您想在操作完成后继续在原始上下文上执行(并且可能使用返回值),这正是 await 所做的。如果你只想在 ThreadPool 线程上执行一个方法(这样你的 UI 不会被阻塞),去

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

and you'll be fine.

你会没事的。

回答by BradleyDotNET

As noted in the other answers, and by this excellent blog postyou want to avoid using async voidoutside of UI event handlers. If you want a safe"fire and forget" asyncmethod, consider using this pattern (credit to @ReedCopsey; this method is one he gave to me in a chat conversation):

正如其他答案中所指出的,在这篇优秀的博客文章中,您希望避免async void在 UI 事件处理程序之外使用。如果您想要一种安全的“即发async即弃”方法,请考虑使用此模式(归功于@ReedCopsey;此方法是他在聊天对话中给我的):

  1. Create an extension method for Task. It runs the passed Taskand catches/logs any exceptions:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. Always use Taskstyle asyncmethods when creating them, never async void.

  3. Invoke those methods this way:

    MyTaskAsyncMethod().FireAndForget();
    
  1. Task.创建扩展方法。它运行传递的Task并捕获/记录任何异常:

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

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

    MyTaskAsyncMethod().FireAndForget();
    

You don't need to awaitit (nor will it generate the awaitwarning). It will also handle any errors correctly, and as this is the only place you ever put async void, you don't have to remember to put try/catchblocks everywhere.

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

This also gives you the option of notusing the asyncmethod as a "fire and forget" method if you actually want to awaitit normally.

如果您真的想要正常使用,这也为您提供了将该async方法用作“即发即弃”方法的选项await