我有一个不返回数据的 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()
会导致 Visual Studio 中出现“Because this call is not awaited, the current method continues to run before the call is completed”警告。在该警告的页面上,它指出:
仅当您确定不想等待异步调用完成并且被调用的方法不会引发任何异常时,才应考虑取消警告。
我确定我不想等待通话完成;我不需要也没有时间。但是调用可能会引发异常。
我偶然发现了这个问题几次,我确信这是一个常见的问题,必须有一个共同的解决方案。
如何在不等待结果的情况下安全地调用异步方法?
更新:
对于建议我等待结果的人来说,这是响应我们 Web 服务 (ASP.NET Web API) 上的 Web 请求的代码。在 UI 上下文中等待使 UI 线程保持空闲,但在 Web 请求调用中等待将等待任务完成后再响应请求,从而无缘无故地增加响应时间。
MyAsyncMethod().Wait()
Task.Run()
?
如果你想“异步”获取异常,你可以这样做:
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)
的含义。
您应该首先考虑将 GetStringData
设为 async
方法,并使其 await
成为从 MyAsyncMethod
返回的任务。
如果您绝对确定不需要处理来自 MyAsyncMethod
或的异常,知道它何时完成,那么您可以这样做:
public string GetStringData()
{
var _ = MyAsyncMethod();
return "hello world";
}
顺便说一句,这不是“常见问题”。很少有人想要执行一些代码而不关心它是否完成而不关心它是否成功完成。
更新:
由于您使用 ASP.NET 并希望早点返回,您可以找到我的 blog post on the subject useful。但是,ASP.NET 并不是为此而设计的,并且没有保证您的代码将在返回响应后运行。 ASP.NET 会尽最大努力让它运行,但不能保证。
所以,对于简单的事情来说,这是一个很好的解决方案,比如将一个事件扔到一个日志中,如果你在这里和那里丢失一些事件并不重要。对于任何类型的关键业务操作来说,这都不是一个好的解决方案。在这些情况下,您必须采用更复杂的体系结构,以持久的方式保存操作(例如,Azure 队列、MSMQ)和单独的后台进程(例如,Azure Worker Role、Win32 服务)来处理它们。
var _ = MyAsyncMethod();
替换为 _ = MyAsyncMethod();
。这仍然避免了警告 CS4014,但它更明确地表明您没有使用该变量。
Peter Ritchie 的回答正是我想要的,Stephen Cleary's article 关于尽早返回 ASP.NET 非常有帮助。
然而,作为一个更普遍的问题(不是特定于 ASP.NET 上下文),以下控制台应用程序使用 Task.ContinueWith(...)
演示了彼得的答案的用法和行为
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()
中抛出的异常在 OnMyAsyncMethodFailed(Task task)
中处理,而 not 在 GetStringData()
周围的 try
/catch
中处理
Console.ReadLine();
并在 MyAsyncMethod
中添加一点睡眠/延迟,您将永远不会看到异常。
我最终得到了这个解决方案:
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);
}
这称为“一劳永逸”,为此有一个 extension。
消耗一个任务并且不做任何事情。对于异步方法中对异步方法的即发即弃调用很有用。
安装 nuget package。
利用:
MyAsyncMethod().Forget();
编辑:有 another way 我最近一直在使用:
_ = MyAsyncMethod();
不是最佳实践,您应该尝试避免这种情况。
但是,为了解决“在没有等待的情况下在 C# 中调用异步方法”的问题,您可以在 Task.Run
中执行异步方法。这种方法将等到 MyAsyncMethod
完成。
public string GetStringData()
{
Task.Run(()=> MyAsyncMethod()).Result;
return "hello world";
}
await
异步解开任务的 Result
,而仅使用 Result 会阻塞,直到任务完成。
如果你想把它包装在一个辅助类中:
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";
}
MyAsyncMethod().Result
不会做同样的事情吗?
我在这里聚会迟到了,但是我一直在使用一个很棒的库,我在其他答案中没有看到它
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 解决方案非常有用
我想问题出现了,你为什么需要这样做?在 C# 5.0 中使用 async
的原因是您可以等待结果。这个方法实际上并不是异步的,只是简单的一次调用,以免过多干扰当前线程。
也许启动一个线程并让它自己完成可能会更好。
async
不仅仅是“等待”结果。 “await”意味着“await”之后的行在调用“await”的同一线程上异步执行。当然,这可以在没有“等待”的情况下完成,但是您最终会拥有一堆委托并失去代码的顺序外观(以及使用 using
和 try/catch
.. .
await
关键字,也不使用 async
关键字,但是使用 async
关键字而不使用 { 1} 在该方法的定义中。
async
不仅仅是“等待”结果。” async
关键字(您通过将其括在反引号中来暗示它是关键字)意味着什么都没有,只不过是在等待结果。作为一般的 CS 概念,它是异步的,这意味着不仅仅是等待结果。
async
创建一个状态机来管理异步方法中的任何等待。如果方法中没有 await
,它仍会创建该状态机——但该方法不是异步的。如果 async
方法返回 void
,则无需等待。因此,它更多而不仅仅是等待结果。
async
关键字。您需要做的所有事情(以及在这种特殊情况下最终使用状态机真正发生的事情)是该方法同步运行,然后包装在一个已完成的任务中。我想从技术上讲,您不只是删除状态机;您删除状态机,然后调用 Task.FromResult
。我假设您(以及编译器编写者)可以自己添加附录。
在具有消息循环的技术上(不确定 ASP 是否是其中之一),您可以阻塞循环并处理消息,直到任务结束,并使用 ContinueWith 解除对代码的阻塞:
public void WaitForTask(Task task)
{
DispatcherFrame frame = new DispatcherFrame();
task.ContinueWith(t => frame.Continue = false));
Dispatcher.PushFrame(frame);
}
这种方法类似于阻止 ShowDialog 并仍然保持 UI 响应。
通常异步方法返回 Task 类。如果您使用 Wait()
方法或 Result
属性并且代码抛出异常 - 异常类型被包装到 AggregateException
- 那么您需要查询 Exception.InnerException
以找到正确的异常。
但也可以改用 .GetAwaiter().GetResult()
- 它也会等待异步任务,但不会包装异常。
所以这里是一个简短的例子:
public async Task MyMethodAsync()
{
}
public string GetStringData()
{
MyMethodAsync().GetAwaiter().GetResult();
return "test";
}
您可能还希望能够从异步函数返回一些参数 - 这可以通过在异步函数中提供额外的 Action<return type>
来实现,例如:
public string GetStringData()
{
return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}
public async Task<String> MyMethodWithReturnParameterAsync()
{
return "test";
}
请注意,异步方法通常具有 ASync
后缀命名,只是为了能够避免同名同步函数之间的冲突。 (例如 FileStream.ReadAsync
)- 我已更新函数名称以遵循此建议。
也许我太天真了,但是,您不能创建一个在调用 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() 快速返回。
async void
的一种过于复杂的方式,因此行为从即发即弃更改为即发即崩溃。您可以通过如下简单的方式实现相同的目的:async void OnErrorCrash(this Task task) => await task;
这很简单,只需调用 asyncMethod().Result 即可调用而无需等待。下面是示例代码,here 是小提琴
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");
}
}
解决方案是将 HttpClient 启动到另一个没有 sincronization 上下文的执行任务中:
var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);
Task
上的扩展方法: public static class AsyncUtility { public static void PerformAsyncTaskWithoutAwait(this Task task, Action