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

本文关键字:任务 注意事项 等待 异步方法 | 更新日期: 2023-09-27 18:30:37

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

方法签名现在看起来类似于这样:

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

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

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

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

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

如果Put有可能出于任何原因抛出任何其他异常,并且您每次将对象插入缓存时都不使用 await Put,则异常将被吞噬在返回的Task中,该没有等待。如果您使用的是 .NET 4.0,则此异常将在该Task.的终结器中重新引发。如果您使用的是 .NET 4.5,则只会忽略它(这可能不是可取的)。

想确保我不会因为拥有太多而受到任何处罚 许多线程或类似的东西。

我这么说只是为了把事情说清楚。当你使用Task.Delay时,你不会旋转任何新线程。Task并不总是等于正在纺的新线。特别是在这里,Task.Delay内部使用Timer,因此没有任何线程开销(如果您确实使用await,则当前延迟的线程除外)。

警告告诉你,在一个你可能实际上不想开火和忘记的地方,你正在着火和忘记行为。 如果您确实想触发并忘记并且不知道操作何时完成,或者甚至是否成功完成的情况下继续操作没有问题,那么您可以安全地忽略警告。

发布任务以未等待运行的一个负面结果是编译器警告本身 - 75 个编译器警告本身就是一个问题,它们隐藏了真正的警告。

如果要向编译器发出信号,表明您有意不对任务结果执行任何操作,则可以使用一个简单的扩展方法,该方法不执行任何操作,但满足编译器对显式性的需求。

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

现在您可以致电

UpdateCacheAsync(data).Release();

没有任何编译器警告。

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

推荐 ASP.NET 方式是

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

顺便说一句,没有在线程以外的线程上捕获/重新抛出 ASP.NET 可能会导致服务器进程崩溃/重新启动