不等待异步方法中的任务的注意事项

Jes*_*ter 10 .net c# task-parallel-library fire-and-forget async-await

我正在开发一个Web API项目,该项目使用Azure的托管缓存服务将数据库结果缓存到内存中,以缩短响应时间并减轻数据库的重复流量.尝试在缓存中放入新项时,有时会抛出特定于缓存的异常,代码为DataCacheErrorCode.RetryLater.当然,为了以后重试而不需要阻止这种方法,我做了它asyncawait Task.Delay在稍后再试一次.以前,开发人员已经Thread.Sleep在那里硬编码,这确实会损害应用程序性能.

方法签名看起来与此类似:

public static async Task Put(string cacheKey, object obj)
Run Code Online (Sandbox Code Playgroud)

在此更改之后,我从应用程序中的所有其他位置获得~75个编译器警告,这些警告调用以前的同步版本Put指示:

因为不等待该调用,所以在调用完成之前继续执行当前方法.考虑将'await'运算符应用于调用的结果.

在这种情况下,由于Put没有返回任何内容,因此我有理由让这个操作发生故障,因为我没有看到任何理由阻止执行调用它的方法.我只是想知道是否存在任何危险或陷阱,Task因为Put可以经常调用在后台运行许多这些即发即弃的操作.或者我应该等待,因为99%的时间我不会得到重试错误,并且Task几乎立即完成.我只是想确保我没有因为有太多线程(或类似的东西)而受到任何处罚.

Yuv*_*kov 6

如果由于Put任何原因有可能抛出任何其他异常,并且await Put每次将对象插入缓存时都不使用,则异常将被丢弃在未返回的Task内容中.如果您使用的是.NET 4.0,则会在Finalizer中重新抛出此异常Task..如果您使用的是.NET 4.5,它将被忽略(这可能并不理想).

想要确保我没有因为有太多线程或类似的东西而受到任何处罚.

我只是这样说清楚.使用时Task.Delay,您不会旋转任何新线程.A Task并不总是等于正在旋转的新线程.具体在这里,Task.Delay内部使用a Timer,因此没有任何线程开销(如果你使用的话,当前被延迟的线程除外await).

  • @alexm它在经过处理程序的时间过后很短的时间内使用线程池线程.它在等待时不消耗线程池线程.这意味着线程只有在有生产性工作时才会被使用,这正是您想要的(并且无法避免). (2认同)
  • 我不鼓励`等待Task.Run(..)`在ASP.NET上伪造`async`处理程序,这是尝试使用`Task.Run`的绝大部分.唯一真正的"Run"用例是"即发即弃".很容易99%的即发即弃的ASP.NET场景应该*不会使用"即发即弃".更新缓存是*极少数*可以接受fire-and-forget(和`Task.Run`)的用例之一.关于请求上下文,我的意思是该请求的`AspNetSynchronizationContext`,[使用`async`时默认捕获](http://blog.stephencleary.com/2012/02/async-and-await.html) (正如我在博客中描述的那样). (2认同)
  • @YuvalItzchakov:大致相当于.像`await Task.Yield().ConfigureAwait(false)`将产生当前线程并在线程池线程上恢复该方法; 而`Task.Run`会将其委托排队到线程池.我认为`Task.Run`有一个更清晰的意图(没有上下文的代码是分开的),但`ConfigureAwait`可能更有效. (2认同)