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

时间:2014-11-17 17:28:35

标签: c# .net async-await task-parallel-library fire-and-forget

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

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

public static async Task Put(string cacheKey, object obj)

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

  

由于未等待此调用,因此在完成调用之前会继续执行当前方法。考虑将'await'运算符应用于调用的结果。

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

4 个答案:

答案 0 :(得分:6)

如果Put因任何原因而有可能抛出任何其他异常,并且每次您将对象插入缓存时都不会使用await Put ,异常将被遗忘在尚未等待的返回Task内。如果您使用的是.NET 4.0,则会在Task.的Finalizer中重新抛出此异常。如果你正在使用.NET 4.5,它将被忽略(这可能是不可取的)。

  

要确保我不会因此而受到任何处罚   许多线程或类似的东西。

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

答案 1 :(得分:2)

警告告诉你,在你可能实际上不想发射和忘记的地方,你会着火并忘记行为。如果你确实想要解雇并忘记并且在没有知道操作何时结束或者甚至成功完成的情况下仍然没有问题,那么你可以放心地忽略警告。

答案 2 :(得分:1)

释放任务以使其无法运行的一个负面结果是编译器警告本身 - 编译器警告本身就是一个问题,它们隐藏了真正的警告。

如果你想告诉编译器你故意没有对任务的结果做任何事情,你可以使用一个什么都不做的简单扩展方法,但是满足编译器对显式性的要求

// Do nothing.
public static void Release(this Task task)
{
}

现在你可以打电话了

UpdateCacheAsync(data).Release();

没有任何编译器警告。

https://gist.github.com/lisardggY/396aaca7b70da1bbc4d1640e262e990a

答案 3 :(得分:0)

推荐的ASP.NET方式是

HostingEnvironment.QueueBackgroundWorkItem(WorkItem);

...

async Task WorkItem(CancellationToken cancellationToken)
{
    try { await ...} catch (Exception e) { ... }
}

BTW没有捕获/重新抛出线程,然后ASP.NET线程可能导致服务器进程崩溃/重新启动