CancellationTokenSource的ObjectDisposedException怎么避免?

星降
发布: 2025-09-19 10:13:01
原创
345人浏览过

避免cancellationtokensource的objectdisposedexception的核心是精准管理其生命周期,确保在所有依赖它的操作完成前不被提前释放;2. 局部使用时应采用using语句,确保using块结束时自动dispose;3. 跨方法传递时只传递cancellationtoken而非cancellationtokensource,防止外部误调用dispose;4. 对于长期存在或共享的cancellationtokensource,应在所属对象的dispose方法中统一释放;5. 使用createlinkedtokensource创建的组合令牌源也需用using管理或在适当时机手动dispose;6. 只有当所有使用该令牌的任务已完成、被取消或明确不再需要时,才可安全调用dispose,避免多线程竞态导致异常。只要遵循“谁创建谁负责、及时释放、不传递源对象”的原则,就能有效杜绝此类异常的发生。

CancellationTokenSource的ObjectDisposedException怎么避免?

CancellationTokenSource
登录后复制
ObjectDisposedException
登录后复制
,说白了,就是你试图对一个已经被“销毁”的
CancellationTokenSource
登录后复制
对象(或者它生成的
CancellationToken
登录后复制
)进行操作。这就像你把水龙头关了,甚至把水管都拆了,然后又想去拧水龙头出水一样,肯定会出问题。避免它的核心,在于精准掌握
CancellationTokenSource
登录后复制
的生命周期,确保在任何依赖它的操作完成之前,它都保持“活着”的状态。

解决方案

要彻底避免

CancellationTokenSource
登录后复制
ObjectDisposedException
登录后复制
,关键在于理解它的
IDisposable
登录后复制
特性以及它与异步操作的协作方式。我通常会从几个层面来思考这个问题:

  1. 就地取材,立即销毁:

    using
    登录后复制
    语句是你的好朋友。 如果你的
    CancellationTokenSource
    登录后复制
    (CTS)的生命周期是局部的,比如它只在一个方法内部被创建和使用,那么最简单、最安全的方式就是把它放在
    using
    登录后复制
    语句块里。这保证了无论代码如何退出(正常完成、抛出异常),CTS都会被正确地
    Dispose
    登录后复制
    掉。

    public async Task DoSomethingCancellable()
    {
        // 假设这个操作最多运行5秒
        using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
        {
            try
            {
                // 把token传给需要支持取消的操作
                await Task.Delay(TimeSpan.FromSeconds(10), cts.Token);
                Console.WriteLine("操作完成。");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("操作被取消了!");
            }
            // using块结束时,cts会自动Dispose,非常省心。
        }
    }
    登录后复制

    这种方式几乎能覆盖大部分简单的场景,因为它强制了资源的及时释放,并且避免了手动管理

    Dispose
    登录后复制
    时机可能引入的错误。

  2. 跨越边界,传递

    Token
    登录后复制
    而非
    Source
    登录后复制
    这是一个非常重要的原则。当你有一个
    CancellationTokenSource
    登录后复制
    时,你通常会把它的
    Token
    登录后复制
    (即
    cts.Token
    登录后复制
    )传递给其他方法或任务,而不是直接传递
    CancellationTokenSource
    登录后复制
    实例本身。为什么?因为
    CancellationTokenSource
    登录后复制
    负责管理取消操作的生命周期和状态,而
    CancellationToken
    登录后复制
    只是一个“信号牌”,它只负责传递“取消”的意图。如果把
    CancellationTokenSource
    登录后复制
    传出去,接收方可能会不小心调用
    Dispose()
    登录后复制
    ,导致你的源头被提前销毁,从而引发
    ObjectDisposedException
    登录后复制

    // 错误示范:把CancellationTokenSource传了出去
    public async Task ProcessData(CancellationTokenSource cts)
    {
        // 某个不负责任的开发者可能在这里调用了cts.Dispose()
        // 导致上游或其它地方再用这个cts时出问题
        // cts.Dispose(); // 比如这里不小心写了这句
        await Task.Delay(1000, cts.Token);
    }
    
    // 正确示范:只传递CancellationToken
    public async Task ProcessDataSafe(CancellationToken token)
    {
        // token是只读的,你无法Dispose它,也无法通过它来触发取消(除非它是一个可取消的token)
        // 这样就保证了CancellationTokenSource的生命周期由它的拥有者管理
        await Task.Delay(1000, token);
    }
    登录后复制

    通过这种方式,

    CancellationTokenSource
    登录后复制
    的拥有者就有了唯一的
    Dispose
    登录后复制
    权,大大降低了误操作的风险。

  3. 长期存在或共享的

    CancellationTokenSource
    登录后复制
    :精细化管理。 对于那些需要在应用程序生命周期内长期存在,或者在多个组件间共享的
    CancellationTokenSource
    登录后复制
    using
    登录后复制
    语句就不适用了。比如,一个全局的应用程序关闭取消令牌,或者一个服务级别的操作取消令牌。 这时候,你需要在一个明确定义的生命周期点来调用
    Dispose
    登录后复制
    。例如,在应用程序启动时创建它,在应用程序关闭时(比如
    IHostApplicationLifetime.ApplicationStopped
    登录后复制
    事件中)调用它的
    Dispose
    登录后复制
    方法。

    // 假设这是一个服务,它有一个全局的CancellationTokenSource
    public class MyBackgroundService : BackgroundService, IDisposable
    {
        private readonly CancellationTokenSource _appCts = new CancellationTokenSource();
    
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // 组合应用停止令牌和我们自己的令牌
            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                stoppingToken, _appCts.Token);
    
            try
            {
                await DoWorkAsync(linkedCts.Token);
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("后台服务工作被取消了。");
            }
        }
    
        public void StopMyWork()
        {
            // 外部调用这个方法来取消服务内部的工作
            _appCts.Cancel();
        }
    
        public void Dispose()
        {
            // 确保在服务实例被销毁时,CancellationTokenSource也被Dispose
            // 避免资源泄露,比如内部的WaitHandle
            _appCts.Dispose();
        }
    }
    登录后复制

    这里,

    _appCts
    登录后复制
    Dispose
    登录后复制
    被放在了
    IDisposable
    登录后复制
    接口的实现中,这意味着当这个服务实例不再需要时,它的资源会被正确释放。

CancellationTokenSource
登录后复制
为什么需要被Dispose?

这个问题问得很好,毕竟不是所有对象都需要显式

Dispose
登录后复制
CancellationTokenSource
登录后复制
之所以需要被
Dispose
登录后复制
,主要是因为它内部可能持有操作系统资源,比如一个
WaitHandle
登录后复制
。在Windows上,
WaitHandle
登录后复制
是内核对象,它们不像普通的内存那样由.NET的垃圾回收器自动管理。如果不显式
Dispose
登录后复制
,这些内核对象就不会被及时释放,长此以往,可能会导致资源泄露,特别是在创建大量
CancellationTokenSource
登录后复制
实例的应用程序中,这可能导致句柄泄露,最终影响系统性能甚至稳定性。

虽然在很多简单场景下,如果你不

Dispose
登录后复制
,垃圾回收器最终也会通过终结器(Finalizer)来清理这些资源,但这并不是一个确定性的过程,你无法控制清理的时机。对于需要确定性资源管理的场景,或者那些可能创建大量短期对象的场景,显式
Dispose
登录后复制
是最佳实践,它能确保资源在不再需要时立即被释放,避免潜在的性能问题和资源耗尽。

在异步操作中,如何安全地管理
CancellationTokenSource
登录后复制
的生命周期?

在异步编程中,管理

CancellationTokenSource
登录后复制
的生命周期确实需要一些技巧,因为操作可能在后台长时间运行,或者被取消。

一个核心的考量是:谁拥有

CancellationTokenSource
登录后复制
,谁就负责
Dispose
登录后复制
它。

智谱清言 - 免费全能的AI助手
智谱清言 - 免费全能的AI助手

智谱清言 - 免费全能的AI助手

智谱清言 - 免费全能的AI助手2
查看详情 智谱清言 - 免费全能的AI助手
  1. 方法内部的局部生命周期: 这是最常见的,也是最简单的。如果一个

    CancellationTokenSource
    登录后复制
    只在一个方法内部使用,并且它的作用域仅限于该方法,那么
    using
    登录后复制
    语句就是最安全、最简洁的选择。它确保了无论操作是成功完成、被取消还是抛出异常,
    CancellationTokenSource
    登录后复制
    都会在方法退出时被正确处理。

  2. 组件或服务层面的生命周期:

    CancellationTokenSource
    登录后复制
    的生命周期与某个组件或服务的生命周期绑定时,比如一个后台任务服务,或者一个处理特定业务流程的管理器。在这种情况下,
    CancellationTokenSource
    登录后复制
    通常会作为该组件的一个私有字段,并在组件的
    Dispose
    登录后复制
    方法中进行清理。这确保了当整个组件被销毁时,相关的取消资源也被释放。比如在ASP.NET Core的
    IHostedService
    登录后复制
    实现中,你可能会在
    StopAsync
    登录后复制
    方法中触发取消,并在
    Dispose
    登录后复制
    方法中清理
    CancellationTokenSource
    登录后复制

  3. 组合取消令牌:

    CancellationTokenSource.CreateLinkedTokenSource
    登录后复制
    的考量。 当你使用
    CreateLinkedTokenSource
    登录后复制
    来组合多个
    CancellationToken
    登录后复制
    时,比如一个用户操作的取消令牌和一个系统全局的关闭令牌,由
    CreateLinkedTokenSource
    登录后复制
    生成的新
    CancellationTokenSource
    登录后复制
    也需要被
    Dispose
    登录后复制
    。它同样可能持有资源。

    // 场景:一个长时间运行的报告生成任务,可以被用户取消,也可以在应用关闭时自动取消
    public async Task GenerateReportAsync(CancellationToken userCancellationToken)
    {
        // 获取应用程序的停止令牌(比如来自IHostApplicationLifetime)
        var appStoppingToken = _hostApplicationLifetime.ApplicationStopping;
    
        // 组合两个令牌:只要其中一个被取消,任务就取消
        using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
            userCancellationToken, appStoppingToken))
        {
            try
            {
                Console.WriteLine("开始生成报告...");
                await Task.Delay(TimeSpan.FromSeconds(30), linkedCts.Token); // 模拟长时间操作
                Console.WriteLine("报告生成完成。");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("报告生成被取消了。");
            }
            // linkedCts在这里会被Dispose
        }
    }
    登录后复制

    这里,

    linkedCts
    登录后复制
    的生命周期被限制在
    GenerateReportAsync
    登录后复制
    方法内部,由
    using
    登录后复制
    语句自动管理。这是一种非常稳健的做法。

CancellationTokenSource
登录后复制
的Dispose方法何时调用是安全的?

这是一个核心问题,也是最容易出错的地方。调用

CancellationTokenSource
登录后复制
Dispose
登录后复制
方法,必须确保所有可能观察或依赖于其
CancellationToken
登录后复制
的操作都已完成、被取消或明确不再需要该令牌

  1. 操作完成或取消之后: 这是最理想的时机。如果你有一个

    Task
    登录后复制
    正在使用这个
    CancellationToken
    登录后复制
    ,那么在那个
    Task
    登录后复制
    最终进入
    RanToCompletion
    登录后复制
    Canceled
    登录后复制
    Faulted
    登录后复制
    状态之后,你就可以安全地
    Dispose
    登录后复制
    掉对应的
    CancellationTokenSource
    登录后复制

    var cts = new CancellationTokenSource();
    var longRunningTask = Task.Run(async () =>
    {
        try
        {
            await Task.Delay(5000, cts.Token); // 模拟一个耗时操作
            Console.WriteLine("任务完成。");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("任务被取消。");
        }
    });
    
    // 假设在某个时刻我们决定取消它
    cts.Cancel();
    
    // 等待任务结束,无论它是完成还是被取消
    await longRunningTask;
    
    // 任务结束后,再Dispose CancellationTokenSource
    cts.Dispose(); // 现在是安全的
    登录后复制

    如果你在

    longRunningTask
    登录后复制
    完成之前就调用了
    cts.Dispose()
    登录后复制
    ,那么
    Task.Delay
    登录后复制
    内部尝试访问已
    Dispose
    登录后复制
    cts.Token
    登录后复制
    时,就可能抛出
    ObjectDisposedException
    登录后复制

  2. 当令牌不再被任何地方观察时: 如果你通过

    token.Register()
    登录后复制
    注册了回调,或者将令牌传递给了某个长期运行的组件,你需要确保这些注册的回调不再被触发,或者这些组件不再持有对令牌的引用。这通常意味着组件自身的生命周期结束,或者它们明确地“注销”了对令牌的观察。

  3. 避免竞态条件: 在多线程或并发环境中,一个线程可能正在使用

    CancellationToken
    登录后复制
    ,而另一个线程却在同时调用
    Dispose
    登录后复制
    。这就会导致
    ObjectDisposedException
    登录后复制
    。解决这种竞态条件通常需要更高级的同步机制,或者更严格的生命周期管理约定。但最根本的还是回到第一点:确保所有依赖操作都已终结。

总的来说,处理

CancellationTokenSource
登录后复制
Dispose
登录后复制
时机,需要一种“责任制”的思维。谁创建,谁负责管理;谁使用,谁就得尊重其生命周期。只要你遵循这个原则,并结合
using
登录后复制
语句的便利性,以及对长期持有对象的精细化管理,
ObjectDisposedException
登录后复制
就很少会来找你的麻烦了。

以上就是CancellationTokenSource的ObjectDisposedException怎么避免?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号