Fire-and-forget with async vs “old async delegate”

大憨熊 提交于 2019-11-25 21:47:18

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".

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"

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

Task.Run(A);

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

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

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

  3. Invoke those methods this way:

    MyTaskAsyncMethod().FireAndForget();
    

You don't need to await it (nor will it generate the await warning). 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/catch blocks everywhere.

This also gives you the option of not using the async method as a "fire and forget" method if you actually want to await it normally.

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

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

and you'll be fine.

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.

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'.

    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;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!