【发布时间】:2021-04-06 09:12:28
【问题描述】:
这里讨论的代码是用 C# 编写并使用 .netcore 3.1 执行的
我有以下代码,它在后台启动工作负载而不等待它完成(即发即弃):
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, token);
}
有三个重点:
- 后台工作以一劳永逸的方式启动。我对等待它的完成不感兴趣
- 提供的取消令牌很重要,后台工作必须在传入的取消请求中列出
- 无论后台工作的结果如何,必须尽快释放提供的资源 (
IAsyncDisposable)。为了释放资源,需要调用DisposeAsync。
此代码的问题在于取消令牌被传递给Task.Run 调用。如果令牌在异步委托开始执行之前被取消,则异步委托永远不会执行,因此finally 块永远不会执行。这样做不会满足释放IAsyncDisposable 资源的要求(基本上,永远不会调用DisposeAsync)。
解决此问题的最简单方法是不在调用 Task.Run 时提供取消令牌。这样,异步委托总是被执行,因此 finally 块也被执行。异步委托内部的代码监听取消请求,所以也满足取消执行的要求:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, CancellationToken.None);
}
我在问自己是否应该将IAsyncDisposable 资源的释放委托给延续任务。使用这种方法重构的代码如下:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
},
token).ContinueWith(async _ =>
{
// release the IAsyncDisposable resource here, afte the completion of the antecedent task and regardless
// of the antecedent task actual state
await resource.DisposeAsync();
});
}
我对@987654333@ gotchas 不是很熟悉,所以我的问题如下:
- 我是否可以保证继续始终执行,即使取消令牌在前面的任务开始执行之前被取消?
- 为调用
ContinueWith提供异步委托有什么问题吗?异步委托的执行是否按预期完全完成? - 最好的方法是什么?将
CancellationToken.None传递给Task.Run的调用,或者通过使用ContinueWith依赖延续?
重要提示:我知道在服务器应用程序中使用Task.Run 不是最好的方法(更多信息可以在here 找到),所以有可能是设计我的整体架构的更好方法。我发布这个问题是为了更好地理解 ContinueWith 的实际行为,因为我并不真正熟悉它的用法(在现代 .NET 代码中,它在很大程度上被 async await 的用法所取代)。
【问题讨论】:
-
老实说,
ContinueWith是遗留 API,如果有的话,不应该真正使用太多;如果是我,我会在Task.Run调用中丢失token,而只使用await using(或try/finally/await resource.DisposeAsync(),如果逻辑特别复杂)insideasynclambda。 -
免责声明在这里确实很重要,因为您肯定在这里做了一些可疑和代码审查失败的事情
-
作为旁注,您不需要将
CancellationToken.None作为参数传递。Task.Run方法有一个只接受一个参数的重载(action委托),这也是最常用的重载。
标签: c# asynchronous async-await task-parallel-library cancellation-token