使用异步vs"旧异步委托"进行即发即弃

mmi*_*mix 58 c# asynchronous c#-5.0

我试图用一种新的语法替换我的旧的"即发即忘"调用,希望更简单,它似乎在逃避我.这是一个例子

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

两种方法都做同样的事情,但是我似乎已经获得了(没有必要EndInvoke和关闭处理,这仍然有点争议)我正在以新的方式失去等待a Task.Yield(),这实际上带来了一个新的问题必须重写所有现有的异步F&F方法只是为了添加一个单行.在性能/清理方面是否有一些无形的收获?

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

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

Ste*_*ary 84

避免async void.它有关于错误处理的棘手语义; 我知道有些人称之为"火与忘记",但我通常使用"火灾和崩溃"这个短语.

问题是:给定一个同步方法A(),如何使用async/await以一种即发即忘的方式异步调用它,而不会得到比"旧方法"更复杂的解决方案

你不需要async/ await.只需将其称为:

Task.Run(A);
Run Code Online (Sandbox Code Playgroud)

  • 那个警告就在那里,因为`async`方法中的"一劳永逸"几乎肯定是一个错误.如果你肯定*确定你想做什么,你可以将结果分配给一个未使用的局部变量,如:`var _ = Task.Run(A);` (20认同)
  • 如果A()中有异步方法调用怎么办? (7认同)
  • 我的意思是,你如何避免不等待任务的警告? (5认同)
  • @AnthonyJohnston:我的意思是*从'async`方法调用*fire-and-forget方法几乎肯定是个错误.在你的情况下,因为你总是处理方法中的异常,所以`async Task`和`async void`之间没什么区别.我仍然会更倾向于"异步任务",因为对我来说`async void`意味着"事件处理程序". (4认同)
  • 但是,如果您想在 UI 线程上而不是在线程池上运行方法 A() 该怎么办?而且您不能等待 A(),因为它将在 UI 线程上异步运行更长时间(不会阻塞)。例如,一个点击处理程序必须触发并忘记调用 A() 并随后执行一些操作。但除此之外不关心 A()。A() 只需要启动即可。 (2认同)

Bra*_*NET 51

正如其他答案中所述,并且通过这篇优秀的博客文章,您希望避免async void在UI事件处理程序之外使用.如果你想要一个安全的 "即发即忘" async方法,请考虑使用这种模式(信用卡@ReedCopsey;这种方法是他在聊天对话中给我的方法):

  1. Task.创建扩展方法.它运行传递Task和捕获/记录任何异常:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 永远不要在创建时使用Task样式async方法async void.

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

    MyTaskAsyncMethod().FireAndForget();
    
    Run Code Online (Sandbox Code Playgroud)

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

如果你真的想要它,你还可以选择使用该async方法作为"即发即弃"的方法await.

  • 好吧,如果我有一个任务,我会跑它,不是吗? (2认同)
  • @mmix这取决于你可以使用`Task`对象并运行它,但那不使用await/async.这就是你用await/async"发射并忘记"的方法.请注意,当您调用Async框架方法时,这会非常有用,并且您希望以"一劳永逸"的方式使用它们. (2认同)
  • 嗨,这是一个老帖子,但一般的想法是使用语言"流"元素来实现火和忘记,而不是隐式使用Task对象.我们得出的结论是,由于调用异步不会引发新的线程直到它等待,所以它不可能.如果我有Task对象,那么我只是Run() - 它会触发并忘记. (2认同)

Dan*_*ber 18

对我来说,似乎"等待"某些东西和"发射并忘记"是两个正交的概念.您可以异步启动方法而不关心结果,或者您希望在操作完成后继续在原始上下文上执行(并且可能使用返回值),这正是await所做的.如果你只想在ThreadPool线程上执行一个方法(这样你的UI就不会被阻止),那就去吧

Task.Factory.StartNew(() => DoIt2("Test2"))
Run Code Online (Sandbox Code Playgroud)

你会没事的

  • @DanBryant:这改变了在.NET 4.5](http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/10217876.aspx).`UnobservedTaskException`将不再崩溃进程; 如果你不处理它,则会默默忽略异常. (8认同)
  • 起初我也有同样的感觉;我花了很长时间才开始欣赏这种设计。未来基于`Task` 的代码将基于`async`;在这个新世界中,一个未被观察到的“任务”*是*一个即发即忘的“任务”。与旧行为一样,这并没有违反快速失败的哲学。默认情况下,旧行为会崩溃,因为某些错误发生在*一些不确定的时间之前*,因此旧行为无论如何都不是“快速失败”。 (3认同)
  • 我试验的越多,它看起来就越多.async仅适用于对异步任务的结果有意义延续的进程.没有延续需要,没有支持(除了Task.Yield()).我想我再次受到营销的打击...... (2认同)