如何安全地调用c#异步方法没有等待

我有一个async方法,它不返回任何数据:

public async Task MyAsyncMethod()
{
// do some stuff async, don't return any data
}

我从另一个返回一些数据的方法调用这个:

public string GetStringData()
{
MyAsyncMethod(); // this generates a warning and swallows exceptions
return "hello world";
}

调用MyAsyncMethod()而不等待它会导致“__abc1”;在visual studio警告。在警告页面上,它写道:

你应该考虑抑制警告只有如果你确定你不想等待异步调用完成被调用的方法不会引发任何异常

我确定我不想等待调用完成;我不需要,也没有时间。但是调用可能引发异常。

我遇到过几次这个问题,我相信这是一个普遍的问题,必须有一个共同的解决方案。

我如何安全地调用异步方法而不等待结果?

更新:

对于那些建议我只是等待结果的人,这是对我们的web服务(ASP。NET Web API)。在UI上下文中等待保持UI线程空闲,但在web请求调用中等待任务完成后才响应请求,因此毫无理由地增加响应时间。

343274 次浏览

我想问题来了,你为什么要这么做?在c# 5.0中使用async的原因是为了等待结果。这个方法实际上不是异步的,只是一次调用,这样就不会过多地干扰当前线程。

也许最好是开始一个线程,让它自己完成。

你应该首先考虑让GetStringData成为一个async方法,并让它awaitMyAsyncMethod返回的任务。

如果你绝对确定你不需要处理MyAsyncMethod 的异常知道它何时完成,那么你可以这样做:

public string GetStringData()
{
var _ = MyAsyncMethod();
return "hello world";
}

顺便说一句,这不是一个“普遍问题”。很少有人想要执行一些代码而不关心它是否完成而且,不关心它是否成功完成。

更新:

既然你是ASP。NET和想要早点返回,你可以找到我的关于这个主题的博客文章很有用。然而,ASP。NET不是为此而设计的,并且没有保证,您的代码将在响应返回后运行。ASP。NET会尽其所能让它运行,但不能保证。

因此,这是一个很好的解决方案,对于一些简单的事情,比如把一个事件扔到一个日志中,如果你在这里和那里丢失了一些,它不会真的问题。对于任何类型的关键业务操作,这都不是一个好的解决方案。在这些情况下,你必须采用一个更复杂的体系结构,以持久的方式保存操作(例如,Azure队列,MSMQ)和一个单独的后台进程(例如,Azure Worker Role, Win32 Service)来处理它们。

如果你想“异步”地获取异常,你可以这样做:

  MyAsyncMethod().
ContinueWith(t => Console.WriteLine(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);

这将允许您处理“main”之外的线程上的异常。线程。这意味着你不必“等待”;用于调用MyAsyncMethod的线程对__abc0的调用;但是,仍然允许您在异常情况下执行某些操作——但仅在发生异常时才可以。

更新:

从技术上讲,你可以用await做类似的事情:

try
{
await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}

...如果你需要特别使用try/catch(或using),这将是有用的,但我发现ContinueWith更明确一点,因为你必须知道ConfigureAwait(false)的意思。

Peter Ritchie的答案是我想要的,并且Stephen Cleary的文章关于在ASP中早期返回。NET非常有用。

然而,作为一个更普遍的问题(不特定于ASP。下面的控制台应用程序使用Task.ContinueWith(...)演示了Peter的答案的用法和行为

static void Main(string[] args)
{
try
{
// output "hello world" as method returns early
Console.WriteLine(GetStringData());
}
catch
{
// Exception is NOT caught here
}
Console.ReadLine();
}


public static string GetStringData()
{
MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
return "hello world";
}


public static async Task MyAsyncMethod()
{
await Task.Run(() => { throw new Exception("thrown on background thread"); });
}


public static void OnMyAsyncMethodFailed(Task task)
{
Exception ex = task.Exception;
// Deal with exceptions here however you want
}

GetStringData()提前返回而不等待MyAsyncMethod(),在MyAsyncMethod()中抛出的异常在try/catch中的OnMyAsyncMethodFailed(Task task)中处理

我最终得到了这个解决方案:

public async Task MyAsyncMethod()
{
// do some stuff async, don't return any data
}


public string GetStringData()
{
// Run async, no warning, exception are catched
RunAsync(MyAsyncMethod());
return "hello world";
}


private void RunAsync(Task task)
{
task.ContinueWith(t =>
{
ILog log = ServiceLocator.Current.GetInstance<ILog>();
log.Error("Unexpected Error", t.Exception);


}, TaskContinuationOptions.OnlyOnFaulted);
}

在带有消息循环的技术(不确定ASP是否是其中之一)上,您可以阻塞循环并处理消息,直到任务结束,并使用ContinueWith来解除阻塞代码:

public void WaitForTask(Task task)
{
DispatcherFrame frame = new DispatcherFrame();
task.ContinueWith(t => frame.Continue = false));
Dispatcher.PushFrame(frame);
}

这种方法类似于在ShowDialog上阻塞并且仍然保持UI的响应性。

这被称为“开火并忘记”,有扩展用于此。

消耗一个任务,但不对它做任何事情。在异步方法中用于对异步方法的“触发-忘记”调用。

安装nuget包

使用:

MyAsyncMethod().Forget();

编辑:有另一种方式我最近一直在使用:

_ = MyAsyncMethod();

解决方案是启动HttpClient到另一个没有同步上下文的执行任务:

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);

这不是最好的做法,你应该尽量避免这种情况。

然而,为了解决“在c#中调用异步方法而不等待”的问题,你可以在Task.Run中执行异步方法。此方法将等待MyAsyncMethod完成。

public string GetStringData()
{
Task.Run(()=> MyAsyncMethod()).Result;
return "hello world";
}

await异步地展开任务的Result,而仅使用结果将阻塞直到任务完成。

如果你想把它包装在一个helper类中:

public static class AsyncHelper
{
public static void Sync(Func<Task> func) => Task.Run(func).ConfigureAwait(false);


public static T Sync<T>(Func<Task<T>> func) => Task.Run(func).Result;


}

然后说

public string GetStringData()
{
AsyncHelper.Sync(() => MyAsyncMethod());
return "hello world";
}

通常异步方法返回Task类。如果你使用Wait()方法或Result属性并且代码抛出异常——异常类型被封装到AggregateException中——那么你需要查询Exception.InnerException来定位正确的异常。

但是也可以使用.GetAwaiter().GetResult()代替- 它也将等待异步任务,但不会包装异常

这里有一个简短的例子:

public async Task MyMethodAsync()
{
}


public string GetStringData()
{
MyMethodAsync().GetAwaiter().GetResult();
return "test";
}

你可能还希望能够从async函数返回一些参数——这可以通过在async函数中提供额外的Action<return type>来实现,例如:

public string GetStringData()
{
return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}


public async Task<String> MyMethodWithReturnParameterAsync()
{
return "test";
}

请注意,异步方法通常有ASync后缀命名,只是为了能够避免同名同步函数之间的冲突。(例如FileStream.ReadAsync) -我已经更新了函数名来遵循这个建议。

我来晚了,但有一个很棒的库,我一直在使用,我没有看到在其他答案中引用

https://github.com/brminnick/AsyncAwaitBestPractices

如果需要“Fire And Forget”,则调用任务上的扩展方法。

将onException动作传递给调用可以确保两全其——不需要等待执行并降低用户速度,同时保留以优雅的方式处理异常的能力。

在你的例子中,你可以这样使用它:

   public string GetStringData()
{
MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
{
//DO STUFF WITH THE EXCEPTION
});
return "hello world";
}

它还提供了可等待的AsyncCommands实现ICommand开箱即用,这是伟大的我的MVVM Xamarin解决方案

也许我太天真了,但是,你不能创建一个事件,当GetStringData()被调用时引发,并附加一个EventHandler,调用和等待异步方法?

喜欢的东西:

public event EventHandler FireAsync;


public string GetStringData()
{
FireAsync?.Invoke(this, EventArgs.Empty);
return "hello world";
}


public async void HandleFireAsync(object sender, EventArgs e)
{
await MyAsyncMethod();
}

在代码的某处附加和分离事件:

FireAsync += HandleFireAsync;


(...)


FireAsync -= HandleFireAsync;

不确定这是否可能是反模式(如果是,请让我知道),但它捕获异常并从GetStringData()快速返回。

这很简单,只需调用asyncMethod()。结果呼叫无等待。下面是示例代码,在这里是小提琴

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
                    

public class Program
{
public static void Main()
{
var asyncDemo = new AsyncDemo();
asyncDemo.TestMethod1Void().Wait();
var result = asyncDemo.TestMethod1().Result;
Console.WriteLine(result);
}
}


public class AsyncDemo {
    

public async Task<string> TestMethod1()
{
Thread.Sleep(1000);
return  "From Async Method";
}
    

public async Task TestMethod1Void()
{
Thread.Sleep(1000);
Console.WriteLine("Async Void Method");
}
}