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

Geo*_*ell 283 c# exception task task-parallel-library async-await

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

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}
Run Code Online (Sandbox Code Playgroud)

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

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

MyAsyncMethod()没有等待的情况下进行呼叫会导致" 因为此呼叫未被等待,当前方法在呼叫完成之前继续运行 "在visual studio中发出警告.在该警告的页面上,它指出:

只有当您确定不想等待异步调用完成并且被调用的方法不会引发任何异常时,才应考虑禁止警告.

我确定我不想等待电话完成; 我不需要或没有时间.但这一呼吁可能引发例外.

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

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

更新:

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

Pet*_*hie 174

如果要"异步"获取异常,可以执行以下操作:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);
Run Code Online (Sandbox Code Playgroud)

这将允许您在"主"线程以外的线程上处理异常.这意味着您不必"等待" MyAsyncMethod()来自调用的线程的调用MyAsyncMethod; 但是,仍允许您使用异常执行某些操作 - 但仅在发生异常时才执行.

更新:

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

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}
Run Code Online (Sandbox Code Playgroud)

...如果您需要专门使用try/ catch(或using),这将是有用的,但我发现它ContinueWith更明确,因为你必须知道什么ConfigureAwait(false)意思.

  • ContinueWith版本与try {await} catch {}版本不同.在第一个版本中,ContinueWith()之后的所有内容都将立即执行.最初的任务被解雇并被遗忘.在第二个版本中,catch {}之后的所有内容只有在初始任务完成后才会执行.第二个版本相当于"等待MyAsyncMethod().ContinueWith(t => Console.WriteLine(t.Exception),TaskContinuationOptions.OnlyOnFaulted).ConfigureAwait(fals); (8认同)
  • 我把它变成了`Task`的扩展方法:public static class AsyncUtility {public static void PerformAsyncTaskWithoutAwait(this Task task,Action <Task> exceptionHandler){var dummy = task.ContinueWith(t => exceptionHandler(t),TaskContinuationOptions. OnlyOnFaulted); 用法:MyAsyncMethod().PerformAsyncTaskWithoutAwait(t => log.ErrorFormat("调用MyAsyncMethod时发生错误:\n {0}",t.异常)); (7认同)
  • 嘿 - 我没有投反对票,但是......你能解释一下你的更新吗?呼叫不应该被等待,所以如果你这样做,那么你就等待呼叫,你只是不要继续捕获的上下文...... (6认同)
  • 确认 Thanasis Ioannidis 的评论最能回答OP问题。@PeterRitchie,我强烈建议更新您接受的答案,以避免被埋在评论中。 (6认同)
  • 投票者。注释?如果答案有问题,我很想知道和/或修复。 (2认同)
  • 我尝试使用“.ConfigureAwait(false);”测试应用程序,但它仍然暂停循环。我发现的唯一解决方案是使被调用的方法“async void”(因此不需要等待),每个人都认为这个解决方案不好。但我认为这是创建独立任务的最佳解决方案。 (2认同)

Ste*_*ary 55

您应该首先考虑制作GetStringData一个async方法并让它await返回任务MyAsyncMethod.

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

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,这不是一个"常见问题".这是非常罕见的要执行一些代码,并不在乎它是否完成,并不在乎是否成功完成.

更新:

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

所以,这是简单的东西喜欢折腾的事件记录到日志在它不罚款的解决方案真的,如果你失去了几这里有关系.对于任何类型的业务关键型操作而言,它都不是一个好的解决方案.在这些情况下,您必须采用更复杂的体系结构,使用持久的方法来保存操作(例如,Azure队列,MSMQ)和单独的后台进程(例如,Azure Worker Role,Win32 Service)来处理它们.

  • @GeorgePowell:在没有活动请求的情况下让代码在ASP.NET上下文中运行是非常危险的.我有一篇[可能会帮助你的博客文章](http://blog.stephencleary.com/2012/12/returning-early-from-aspnet-requests.html),但我不知道你的问题,我可以'说我是否建议采用这种方法. (4认同)
  • 在C#7中,您可以将`var _ = MyAsyncMethod();`替换为`_ = MyAsyncMethod();`.这仍然可以避免警告CS4014,但它使你更明确地表示你没有使用变量. (3认同)
  • 我认为OP的意思是他不希望客户端(HTTP请求)等待将某些内容记录到数据库是否成功。如果日志记录失败,请确保应用程序仍应完全控制该异常的处理,但我们不需要客户端等待该异常的处理。我认为这意味着工作需要在后台线程上完成。是的..保留一个线程来执行异步任务很糟糕,但需要这样做来处理潜在的异常。 (3认同)
  • 我想你可能会误解.如果它抛出异常并失败,我会小心,但我不想在返回数据之前等待该方法.另外,如果有任何不同,请参阅我关于我正在使用的上下文的编辑. (2认同)
  • @StephenCleary我有类似的需求。在我的示例中,我有/需要一个批处理引擎在云中运行,我将“ping”端点以启动批处理,但我想立即返回。由于 ping 它会启动,因此它可以处理从那里开始的所有事情。如果抛出异常,那么它们只会记录在我的“BatchProcessLog/Error”表中...... (2认同)

Geo*_*ell 45

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
}
Run Code Online (Sandbox Code Playgroud)

GetStringData()返回早期,不必等待MyAsyncMethod(),并抛出的异常MyAsyncMethod()是在处理OnMyAsyncMethodFailed(Task task)try/ catch左右GetStringData()

  • 删除`Console.ReadLine();`并在`MyAsyncMethod`中添加一点睡眠/延迟,你永远不会看到异常. (6认同)

Fil*_*dji 16

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

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);
}
Run Code Online (Sandbox Code Playgroud)


Cha*_*thJ 15

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

但是,为了解决“在 C# 中调用异步方法而不需要等待”,您可以在Task.Run. 此方法将等到MyAsyncMethod完成。

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

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;

}
Run Code Online (Sandbox Code Playgroud)

并像这样打电话

public string GetStringData()
{
    AsyncHelper.Sync(() => MyAsyncMethod());
    return "hello world";
}
Run Code Online (Sandbox Code Playgroud)

  • `MyAsyncMethod().Result` 不会做同样的事情吗? (6认同)

was*_*ast 13

这被称为fire and forget,并且有一个扩展

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

安装nuget 包

用:

MyAsyncMethod().Forget();
Run Code Online (Sandbox Code Playgroud)

  • 它没有做任何事情,只是删除警告的一个技巧。请参阅https://github.com/microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading/TplExtensions.cs#L256 (18认同)

Ada*_*ent 6

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

https://github.com/brminnick/AsyncAwaitBestPractices

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

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

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

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

它还提供了可等待的 AsyncCommands 实现 ICommand 开箱即用,这对我的 MVVM Xamarin 解决方案非常有用