当先行任务处于取消状态时执行 ContinueWith,并在 ContinueWith

问题描述

此处讨论的代码是用 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.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();
    });
}

我不太熟悉 ContinueWith 问题,所以我的问题如下:

  1. 我是否可以保证继续总是执行,即使取消标记之前先行任务开始执行?
  2. ContinueWith调用提供异步委托有什么问题吗?异步委托的执行是否按预期完全完成?
  3. 最好的方法是什么?将 CancellationToken.None 传递给 Task.Run调用,还是通过使用 ContinueWith 依赖延续?

重要提示:我知道在服务器应用程序中使用 Task.Run不是的最佳方法(更多信息可以在 here ),所以可能有更好的方法来设计我的整体架构。我发布这个问题是为了更好地理解 ContinueWith 的实际行为,因为我不太熟悉它的用法(在现代 .NET 代码中,它很大程度上被 async await用法所取代)。>

解决方法

您可以考虑使用 await using 语句,自动处理 resource 的异步处理:

public async void StartBackgroundWork(IAsyncDisposable resource,CancellationToken token)
{
    await using var _ = resource;
    try
    {
        await Task.Run(async () => 
        {
            await Task.Delay(1500,token);
        },token);
    } catch (OperationCanceledException) { }
}

我还将您的“即发即弃”任务转换为 async void(又名“即发即失”)方法。万一发生了不可思议的事情并且您的代码有错误,而不是应用程序继续运行并发生未观察到的异常,从而可能导致应用程序状态损坏,整个应用程序将崩溃,迫使您尽快修复错误。

但老实说,用一种方法创建一次性资源并用另一种方法处理它是一种臭味的设计。理想情况下,创建资源的方法应该负责最终处理它。

,

我认为Theodor has a great answer;我只是要回答你的其他一些问题:

我是否可以保证始终执行延续,即使取消令牌在先行任务开始执行之前被取消?

ContinueWith 即使前面的任务已经完成,也会执行它的委托。在这个特定的案例中,仅仅因为一劳永逸的性质就没有“保证”。

为调用 ContinueWith 提供异步委托有什么问题吗?

ContinueWith 不知道 async,因此 ContinueWith 的返回类型对于大多数开发人员来说是令人惊讶的。由于您的代码丢弃了返回类型,因此这里不必担心。

异步委托的执行是否按预期完全完成?

在这种情况下,很有可能,但这实际上取决于“预期”的含义。像所有其他即发即弃的代码一样,您不能保证完成。 ContinueWith 有一个额外的问题:它使用 TaskScheduler 执行它的委托,并且默认的 TaskScheduler 不是 TaskScheduler.Default 而实际上是 TaskScheduler.Current。因此,如果您确实需要使用 TaskScheduler,我总是建议传递明确的 ContinueWith 以保持清晰。

最好的方法是什么?将 CancellationToken.None 传递给 Task.Run 的调用,还是使用 ContinueWith 依赖延续?

只需将第二个参数放到 Task.Run 中即可。

我会更进一步:Task.Run 可能甚至不应该接受 CancellationToken。我还没有看到它有用的场景。我怀疑 API 的 CancellationToken 部分是从 TaskFactory.StartNew 复制的(它很少有用),但由于 Task.Run 总是使用 TaskScheduler.Default,提供 CancellationToken 在实践中没有用。

附言我最近写了一个关于 proper solution for fire-and-forget on ASP.NET 的短系列。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...