c# - 当先行任务处于取消状态时执行 ContinueWith 以及在 ContinueWith 内部使用异步委托(delegate)

标签 c# asynchronous async-await task-parallel-library cancellation-token

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

重要的三点:

  • 后台工作以火后遗忘的方式启动。我没有兴趣等待它完成
  • 提供的取消 token 很重要,后台工作必须列在传入的取消请求中
  • 提供的资源 (IAsyncDisposable) 必须尽快释放,无论后台工作的结果如何。为了释放资源,需要调用 DisposeAsync

此代码的问题是取消标记被传递给 Task.Run 调用。如果 token 在异步委托(delegate)开始执行之前被取消,则异步委托(delegate)永远不会执行,因此 finally block 永远不会执行。这样做不满足释放 IAsyncDisposable 资源的要求(基本上,永远不会调用 DisposeAsync)。

解决此问题的最简单方法是在调用 Task.Run提供取消 token 。这样,异步委托(delegate)总是被执行,因此 finally block 也被执行。 async delegate 里面的代码监听了取消请求,所以也满足了取消执行的要求:

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 资源的释放委托(delegate)给后续任务。使用该方法重构后的代码如下:

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. 我是否可以保证继续总是执行,即使取消 token 在之前前面的任务开始执行也是如此?
  2. ContinueWith 的调用提供异步委托(delegate)有什么问题吗?异步委托(delegate)的执行是否按预期完全完成?
  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(也称为即发即崩溃)方法。万一发生不可想象的事情并且您的代码有错误,而不是应用程序继续运行并发生未观察到的异常,可能导致应用程序状态损坏,整个应用程序将崩溃,迫使您尽快修复错误。

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

关于c# - 当先行任务处于取消状态时执行 ContinueWith 以及在 ContinueWith 内部使用异步委托(delegate),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66965815/

相关文章:

c# - 使用 lambda 函数从旧列表创建新列表

c# - JQuery:如何使用 live 和 post 函数

javascript - 仅在获取所有文件后执行命令

asynchronous - 使用 F# 和 Task<T> 的循环并发算法

iOS SDWebImage 淡入新图像

C# 'Cannot access a disposed object' 访问 Slack WebAPI 时出错 - 线程或其他什么?

c# - HttpClient 返回特殊字符但没有可读性

c# - 如何从 VB6 访问 SQL CE 3.5

c# - 为什么我会得到 Soap 异常?

ios - 异步调度队列中只有两个任务并发运行