我有一个异步方法返回没有数据:
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";
}
在visual studio中调用MyAsyncMethod()而不等待它会导致“因为此调用未被等待,当前方法在调用完成之前继续运行”警告。在警告页面上,它写道:
只有在确定不希望等待异步调用完成并且被调用的方法不会引发任何异常时,才应该考虑取消警告。
我确定我不想等待调用完成;我不需要,也没有时间。但这种呼吁可能会引发例外。
我遇到过几次这个问题,我相信这是一个普遍的问题,必须有一个共同的解决方案。
我如何安全地调用异步方法而不等待结果?
更新:
对于那些建议我只是等待结果的人,这是对我们的web服务(ASP。NET Web API)。在UI上下文中等待保持UI线程空闲,但在web请求调用中等待任务完成后才响应请求,因此毫无理由地增加响应时间。
这不是最好的做法,你应该尽量避免这种情况。
然而,为了解决“在c#中不使用await调用异步方法”的问题,你可以在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。定位正确的异常。
但也可以使用.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) -我已经更新了函数名来遵循这个建议。
您应该首先考虑使GetStringData成为一个异步方法,并让它等待MyAsyncMethod返回的任务。
如果你绝对确定你不需要处理MyAsyncMethod的异常,或者知道它什么时候完成,那么你可以这样做:
public string GetStringData()
{
var _ = MyAsyncMethod();
return "hello world";
}
顺便说一句,这不是一个“普遍问题”。很少有人想要执行一些代码而不关心它是否完成,也不关心它是否成功完成。
更新:
既然你是ASP。如果你想早点回来,你可能会发现我关于这个主题的博客文章很有用。然而,ASP。NET并不是为此而设计的,并且不能保证您的代码在返回响应后还能运行。ASP。NET会尽其所能让它运行,但不能保证。
因此,这是一个很好的解决方案,对于一些简单的事情,比如把一个事件扔到一个日志中,即使你在这里或那里丢失了一些也没关系。对于任何类型的关键业务操作,这都不是一个好的解决方案。在这些情况下,你必须采用更复杂的架构,以持久的方式保存操作(例如,Azure队列,MSMQ)和一个单独的后台进程(例如,Azure Worker Role, Win32 Service)来处理它们。
如果你想“异步”地获取异常,你可以这样做:
MyAsyncMethod().
ContinueWith(t => Console.WriteLine(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
这将允许您处理“主线程”以外的线程上的异常。这意味着你不必“等待”从调用MyAsyncMethod的线程调用MyAsyncMethod();但是,仍然允许您在异常情况下执行某些操作——但仅在发生异常时才可以。
更新:
从技术上讲,你可以用await做类似的事情:
try
{
await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
...如果你需要特别使用try/catch(或using),这将是有用的,但我发现ContinueWith更明确一点,因为你必须知道ConfigureAwait(false)的意思。
这不是最好的做法,你应该尽量避免这种情况。
然而,为了解决“在c#中不使用await调用异步方法”的问题,你可以在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";
}