如何在没有等待的情况下在C#中安全地调用异步方法

时间:2013-03-20 11:59:07

标签: c# exception-handling async-await

我有一个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请求调用中等待将在响应请求之前等待Task完成,从而无缘无故地增加响应时间。

12 个答案:

答案 0 :(得分:157)

如果您想“异步”获取异常,可以执行以下操作:

  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)的含义。

答案 1 :(得分:48)

您应首先考虑将GetStringData作为async方法,并让awaitMyAsyncMethod返回任务。

如果您完全确定不需要处理来自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 Service)处理它们。

答案 2 :(得分:43)

Peter Ritchie的回答是我想要的,关于早期返回ASP.NET的Stephen Cleary's article非常有帮助。

作为一个更普遍的问题(不是特定于ASP.NET上下文),以下控制台应用程序使用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()中抛出的异常会在OnMyAsyncMethodFailed(Task task)处理, 处理try } catch

周围的{/ GetStringData()

答案 3 :(得分:15)

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

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);
}

答案 4 :(得分:3)

这就是所谓的“忘了火”,为此,有一个extension

  

使用一项任务,对此不做任何事情。对于异步方法中的异步方法的即发即弃调用很有用。

集成nuget package

使用:

MyAsyncMethod().Forget();

答案 5 :(得分:2)

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

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

答案 6 :(得分:0)

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

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

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

答案 7 :(得分:0)

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

https://github.com/brminnick/AsyncAwaitBestPractices

如果您需要“解雇”,请在任务上调用扩展方法。

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

在您的示例中,您将像这样使用它:

sudo yum install boost boost-devel

它还提供了可以实现ACommand的AsyncCommands,这对我的MVVM Xamarin解决方案非常有用

答案 8 :(得分:-1)

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

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

答案 9 :(得分:-1)

如果您真的想这样做。只需解决“不用等待就在C#中调用异步方法”,您就可以在Task.Run内执行异步方法。这种方法将等到MyAsyncMethod完成。

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

await异步解包任务的Result,而仅使用 Result 会阻塞直到任务完成。

答案 10 :(得分:-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()快速返回。

答案 11 :(得分:-2)

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