You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确保.NET Task在Worker实例被GC回收时自动停止?

问题背景

我通过Task实现了一个后台任务,并使用IAsyncDisposable来停止它,具体实现代码如下:

// implementation
public sealed class Worker : IAsyncDisposable
{
    private readonly CancellationTokenSource _cts;
    private readonly Task _worker;

    public Worker()
    {
        _cts = new CancellationTokenSource();
        _worker = DoWork(_cts.Token);
    }

    public ValueTask DisposeAsync()
    {
        _cts.Cancel();
        _cts.Dispose();
        return new ValueTask(_worker);
    }

    private static async Task DoWork(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            // do async work
        }
    }
}

static async Task CorrectUsage()
{
    await using var worker = new Worker();

    // do more stuff
}

static async Task IncorrectUsage()
{
    var worker = new Worker();

    // do more stuff
}

正常情况下该实现可正常工作,但我发现若调用代码未释放worker实例,可能会导致内存泄漏。我希望当Worker组件被误用(未手动释放)、实例超出作用域被GC回收时,能自动调用_cts.Cancel()终止运行中的任务。

我了解过~Worker()和Dispose(bool disposing)方法,但查阅到的文档均指出这些方法专门用于释放非托管资源。因此我有两个疑问:

  1. 如何让GC回收Worker实例时自动调用_cts.Cancel()?
  2. 若解决方案涉及Dispose(bool disposing),是否有文档说明该方法在disposing: false时的调用逻辑?

注:关于Task与GC的相关参考:.NET Task实例在运行期间是否会超出作用域?


解决方案与答疑

1. 让GC回收时自动取消任务的实现方式

虽然官方文档推荐终结器(~Worker())仅用于清理非托管资源,但你可以在终结器中调用_cts.Cancel()来实现需求——这不属于非托管资源清理的常规场景,但技术上完全可行:取消令牌源是托管对象,但终结器触发其取消逻辑不会引发安全问题。

需要注意:终结器的执行时机完全由GC决定,无法预测,这意味着Worker实例超出作用域后,后台任务可能仍会运行一段时间,直到GC回收实例并执行终结器。

修改后的Worker类实现如下:

public sealed class Worker : IAsyncDisposable
{
    private readonly CancellationTokenSource _cts;
    private readonly Task _worker;
    private bool _isDisposed;

    public Worker()
    {
        _cts = new CancellationTokenSource();
        _worker = DoWork(_cts.Token);
    }

    public ValueTask DisposeAsync()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
        return new ValueTask(_worker);
    }

    ~Worker()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (_isDisposed) return;

        // 无论手动释放还是GC回收,都触发取消
        _cts.Cancel();
        
        if (disposing)
        {
            // 仅在手动释放时清理托管资源(避免终结器中操作已回收的对象)
            _cts.Dispose();
        }

        _isDisposed = true;
    }

    private static async Task DoWork(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            // do async work,需确保异步操作响应取消
            await Task.Delay(1000, token);
        }
    }
}

核心逻辑:

  • 手动调用DisposeAsync时,执行Dispose(true)并抑制终结器,避免重复处理
  • GC回收实例时,终结器调用Dispose(false),仅触发取消操作,不清理托管资源(如_cts.Dispose())

2. Dispose(bool disposing)在disposing: false时的调用逻辑

根据.NET官方规范,Dispose(bool disposing)参数的含义和约束如下:

  • disposing: true:方法由用户主动调用(如DisposeAsync、Dispose),此时可以安全清理托管和非托管资源,因为所有依赖的托管对象仍处于可控状态
  • disposing: false:方法由终结器调用,此时只能清理非托管资源——因为托管对象可能已经被GC回收,状态不确定,调用其方法可能引发空引用或未定义行为

在你的场景中,调用_cts.Cancel()是安全的:CancellationTokenSource.Cancel()仅触发内部的取消信号,不依赖其他托管对象的存活状态,即使_cts后续被GC回收,取消信号已经发出,后台任务会响应并终止。但绝对不能在disposing: false时调用_cts.Dispose(),因为此时_cts可能已被回收,操作会引发异常。


内容的提问来源于stack exchange,提问作者Shane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:13:20