C# 使用async vs";“旧异步委托”;

C# 使用async vs";“旧异步委托”;,c#,asynchronous,c#-5.0,C#,Asynchronous,C# 5.0,我正试图用一种新的语法来取代我以前的fire和forget调用,希望它更简单,但我似乎一直在逃避它。这里有一个例子 class Program { static void DoIt(string entry) { Console.WriteLine("Message: " + entry); } static async void DoIt2(string entry) { await Task.Yield();

我正试图用一种新的语法来取代我以前的fire和forget调用,希望它更简单,但我似乎一直在逃避它。这里有一个例子

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();
    }
}
类程序
{
静态void DoIt(字符串条目)
{ 
Console.WriteLine(“消息:+输入”);
}
静态异步void DoIt2(字符串条目)
{
等待任务;
Console.WriteLine(“Message2:+条目”);
}
静态void Main(字符串[]参数)
{
//老路
动作异步=DoIt;
async.BeginInvoke(“Test”,ar=>{async.EndInvoke(ar);ar.AsyncWaitHandle.Close();},null);
WriteLine(“旧方法主线程调用程序已完成”);
//新方式
DoIt2(“Test2”);
WriteLine(“新方法主线程调用程序完成”);
Console.ReadLine();
}
}
这两种方法都做了同样的事情,但是我似乎得到了什么(不需要
EndInvoke
和关闭handle,这仍然有点争议),我在新的方式中因为不得不等待
Task.Yield()
,而失去了什么,这实际上带来了一个新问题,即必须重写所有现有的异步F&F方法,只需添加一个线性程序。在性能/清理方面是否有一些无形的收益

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


编辑:我现在发现我可能遗漏了一个真正的问题。问题是:给定一个同步方法a(),我如何使用
async
/
wait
异步调用它,而不获得比“旧方法”更复杂的解决方案

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

主要区别在于等待之间的块更类似于调用,而不是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;
    }
在我看来,“等待”和“开火并忘记”是两个正交的概念。您可以异步启动一个方法,而不关心结果,或者希望在操作完成后继续在原始上下文上执行(并可能使用返回值),这正是Wait所做的。如果您只想在线程池线程上执行一个方法(这样您的UI就不会被阻塞),请选择


你会没事的。

避免
async void
。它具有复杂的错误处理语义;我知道有些人管它叫“失火和遗忘”,但我通常用“失火和崩溃”这个词

问题是:给定一个同步方法a(),我如何使用异步/等待异步调用它,而不获得比“旧方法”更复杂的解决方案

您不需要
异步
/
等待
。就这样说吧:

Task.Run(A);

正如在其他答案中所指出的那样,通过这种出色的方式,您希望避免在UI事件处理程序之外使用
async void
。如果你想要一个安全的“火灾和忘记”<代码>异步> /代码>方法,考虑使用这个模式(信用给@ ReedCopsey;这个方法是他在聊天会话中给我的一个):

  • 任务
    创建扩展方法。它运行传递的
    任务
    ,并捕获/记录任何异常:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  • 创建时始终使用
    Task
    style
    async
    方法,切勿使用
    async void

  • 以这种方式调用这些方法:

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


    这也让你可以选择不使用
    async
    方法作为“开火并忘记”方法,如果你真的想
    正常地等待它。

    下面是我根据Ben Adams的推文编写的一个关于构建这样一个构造的类。嗯


    async/await并不是为将同步工作负载卸载到另一个线程而设计的。我在一些非常大的项目中使用了async/await,但没有看到
    线程。Yield
    。我认为这段代码是对AsyncWait哲学的滥用。如果没有异步IO,async/await可能是错误的解决方案;没有合理的理由强迫http请求者等待一个完整的进程完成,以便从一开始就收到可用的响应。其余的可以安全卸载。唯一的问题是异步/等待帮助是否会变得更糟,或者在这种情况下是否无法使用。我必须承认我对这是什么有不同的想法。我不同意这项工作可能需要卸载。我是说将
    async/await
    Task.Yield
    结合使用会有一种不好的味道。使用
    ThreadPool.QueueUserWorkItem
    更适合这里。毕竟,这才是你真正想要做的。。。将工作以合理的最小代码占用发送到线程池,对吗?哦,好的。说句公道话,我误解了你的说法。我想我只是想用async调用一个方法,它会神奇地在另一个线程上启动:)。说到不同
    MyTaskAsyncMethod().FireAndForget();
    
    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);
                }
            }
        }
    }