426

我有一个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 中。在该警告的页面上,它指出:

仅当您确定不想等待异步调用完成并且被调用的方法不会引发任何异常时,才应考虑取消警告。

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

我偶然发现了这个问题几次,我确信这是一个常见的问题,必须有一个共同的解决方案。

如何在不等待结果的情况下安全地调用异步方法?

更新:

对于建议我等待结果的人来说,这是响应我们 Web 服务 (ASP.NET Web API) 上的 Web 请求的代码。在 UI 上下文中等待使 UI 线程保持空闲,但在 Web 请求调用中等待将等待任务完成后再响应请求,从而无缘无故地增加响应时间。

4

12 回答 12

251

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

  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)意思。

于 2013-03-20T12:59:55.417 回答
87

您应该首先考虑创建GetStringData一个async方法并让它awaitMyAsyncMethod.

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

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

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

更新:

由于您使用 ASP.NET 并希望早点返回,您可能会发现我关于该主题的博客文章很有用。但是,ASP.NET 并不是为此而设计的,并且无法保证您的代码在响应返回后会运行。ASP.NET 会尽最大努力让它运行,但不能保证。

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

于 2013-03-20T12:39:02.357 回答
55

Peter Ritchie 的回答正是我想要的,Stephen Cleary关于尽早返回 ASP.NET 的文章非常有帮助。

然而,作为一个更普遍的问题(不是特定于 ASP.NET 上下文),以下控制台应用程序演示了 Peter 的答案的用法和行为,使用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()inOnMyAsyncMethodFailed(Task task)不是try/ catcharound中处理GetStringData()

于 2013-03-20T15:49:46.090 回答
25

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

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);
}
于 2016-03-15T18:22:36.897 回答
15

这被称为“一劳永逸”,并且有一个扩展

消耗一个任务并且不做任何事情。对于异步方法中对异步方法的即发即弃调用很有用。

安装nuget 包

采用:

MyAsyncMethod().Forget();

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

_ = MyAsyncMethod();
于 2019-01-21T09:51:12.287 回答
10

不是最佳实践,您应该尝试避免这种情况。

但是,为了解决“在 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";
}
于 2019-11-01T05:18:14.250 回答
8

我在这里聚会迟到了,但是我一直在使用一个很棒的库,我在其他答案中没有看到它

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 解决方案非常有用

于 2019-12-01T08:24:00.183 回答
1

我想问题出现了,你为什么需要这样做?在 C# 5.0 中的原因async是您可以等待结果。这个方法实际上并不是异步的,只是简单的一次调用,以免过多干扰当前线程。

也许启动一个线程并让它自己完成可能会更好。

于 2013-03-20T12:02:29.673 回答
1

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

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

这种方法类似于阻止 ShowDialog 并仍然保持 UI 响应。

于 2019-01-16T06:34:43.730 回答
-1

解决方案是将 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);
于 2019-07-31T19:29:58.090 回答
-1

通常异步方法返回 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) - 我已更新函数名称以遵循此建议。

于 2019-11-27T20:29:06.367 回答
-1

也许我太天真了,但是,您不能创建一个在调用 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() 快速返回。

于 2020-11-19T10:35:39.453 回答