我有一个异步方法返回没有数据:

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请求调用中等待任务完成后才响应请求,因此毫无理由地增加响应时间。


当前回答

也许我太天真了,但是,你不能创建一个事件,当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()快速返回。

其他回答

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

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

这种方法类似于在ShowDialog上阻塞并且仍然保持UI的响应性。

也许我太天真了,但是,你不能创建一个事件,当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()快速返回。

Peter Ritchie的答案就是我想要的,还有Stephen Cleary关于ASP早期回归的文章。NET非常有用。

然而,作为一个更普遍的问题(不特定于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任务)中处理,而不是在GetStringData()周围的try/catch中处理

您应该首先考虑使GetStringData成为一个异步方法,并让它等待MyAsyncMethod返回的任务。

如果你绝对确定你不需要处理MyAsyncMethod的异常,或者知道它什么时候完成,那么你可以这样做:

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

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

更新:

既然你是ASP。如果你想早点回来,你可能会发现我关于这个主题的博客文章很有用。然而,ASP。NET并不是为此而设计的,并且不能保证您的代码在返回响应后还能运行。ASP。NET会尽其所能让它运行,但不能保证。

因此,这是一个很好的解决方案,对于一些简单的事情,比如把一个事件扔到一个日志中,即使你在这里或那里丢失了一些也没关系。对于任何类型的关键业务操作,这都不是一个好的解决方案。在这些情况下,你必须采用更复杂的架构,以持久的方式保存操作(例如,Azure队列,MSMQ)和一个单独的后台进程(例如,Azure Worker Role, Win32 Service)来处理它们。

通常异步方法返回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) -我已经更新了函数名来遵循这个建议。