【问题标题】:CreateLinkedTokenSource throws ObjectDisposedException. How to correctly and safely dispose a CancellationTokenSource?CreateLinkedTokenSource 引发 ObjectDisposedException。如何正确安全地处理 CancellationTokenSource?
【发布时间】:2016-05-25 15:12:36
【问题描述】:

这里之前已经提出了多个类似的问题。

MSDN 指出 one should always dispose the CancellationTokenSource when done with it.

好的,但是多线程应用程序和异步等待模型会变得有点复杂。

我正在开发一个库。我遇到的问题是我在几个地方使用了CreateLinkedTokenSource,来自用户的CancellationToken。很快,我会这样做,以便在需要更长的时间时可以取消自己的操作。

例子

public async Task<Result> DoAsync(CancellationToken cancellationToken)
{ 
    using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) 
    {
        // here pass linkedTokenSource.Token further down the line 
        var resultTask = sender.DoAsync(linkedTokenSource.Token); 
        var timeoutTask = Task.Delay(timeout); 
        var completed = await Task.WhenAny(resultTask, timeoutTask); 
        if (completed  == timeoutTask) 
        {
            linkedTokenSource.Cancel(); 
            throw TimeoutException(); 
        }

        return await resultTask;
        // from the point of view of this piece of code
        // we're done with the cancellationTokenSource right? 
        // so I need to dispose the source (done here via `using`)
    }
}

但是,在不同的代码部分中,由于竞争条件,碰巧一些线程试图从linkedTokenSource.Token 中取出CreateLinkedTokenSource,从而导致ObjectDisposedException,因为linkedTokenSource 已经在之后被释放TimeoutException 被抛出。
这将以UnobservedTaskException 告终,如果用户侦听未观察到的异常,这将使用户感到困惑。

在每个CreateLinkedTokenSource 上加上try-catch 并让ObjectDisposedException 行静音对我来说似乎又很奇怪。

我的问题是:

  1. 为什么CreateLinkedTokenSource 会抛出这个异常?对此有解释吗?既然CencellationToken 是一个结构,为什么我不能从中创建一个新的源? (即使在cancelToken被取消)。

  2. 关于在这种情况下应该如何处理的任何建议?

【问题讨论】:

  • 最近在 GitHub 的 CoreClr 存储库中对此进行了讨论。在这些情况下,行为已更改为不引发异常。此更改尚未在 Desktop CLR 中。在某个地方,在触发 .NET 修复的 Stack Overflow 上有一个重复的问题。
  • 正如@usr 所说,这是为.NET Core 修复的。只想添加问题链接:github.com/dotnet/coreclr/issues/4167。 Stephen Toub 是个英雄!

标签: c# .net multithreading async-await


【解决方案1】:

这将导致 UnobservedTaskException 如果用户监听未观察到的异常,这将使用户感到困惑。

嗯,有点。每当您使用 Task.WhenAny 时,UnobservedTaskException 几乎总是会成为现实(并放弃不完整的任务,这是使用 Task.WhenAny 的绝大多数时间)。

因此,他们可能会将ObjectDisposedException 报告给UnobservedTaskException,而不是OperationCanceledException。咩;在async 世界中,如果您使用的是Task.WhenAny,那么无论如何您确实需要忽略UnobservedTaskException。此外,许多“不容易取消”的端点将关闭取消请求的底层句柄,这无论如何都会导致 (IIRC) ObjectDisposedException

为什么 CreateLinkedTokenSource 会抛出这个异常?有对此的解释吗?

这是really, really old Microsoft design guidelines that were written with an '80s OOP mindset 的一部分。我从不同意 MS 的 Dispose 指南,我更喜欢 much simpler model,它涵盖了所有相同的用例,而且脑力开销大大减少。

关于在这种情况下应该如何处理的任何建议?

保持原样。 UnobservedTaskException 没什么大不了的。

【讨论】:

    【解决方案2】:

    如我所见,您已通过创建任务删除了其余代码。什么时候,你的代码很可能是这样的:

    public async Task<Result> DoAsync(CancellationToken cancellationToken)
    { 
        using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) 
        {
            await Task.Run(() =>
            {
                // here pass linkedTokenSource.Token further down the line 
                // check if further processing reached some timeout and if it did:
                if (timeout) 
                {
                    linkedTokenSource.Cancel(); 
                    throw TimeoutException(); 
                }
                // from the point of view of this piece of code
                // we're done with the cancellationTokenSource right? 
                // so I need to dispose the source (done here via `using`)
            }
        }
    }
    

    因此,您捕获了一个闭包变量,该变量被用于using 构造。所以它是这样工作的:

    • 您输入了using
    • 你开始你的任务
    • 你立即从方法返回
    • 您为您的方法执行finally 块,其中calls the .Dispose() method 为您的linkedTokenSource 变量执行
    • 您遇到了超时
    • 你访问一个废弃的闭包

    您必须在完成整个任务后重写您的代码以手动处理 linkedTokenSource,而不是在您完成启动它时。

    【讨论】:

    • 嗨,感谢您的回答,我填写了示例中的所有代码,很抱歉没有从一开始就这样做
    • 如果您没有在代码中使用await,为什么要将方法标记为asyncsender 变量的类型是什么?您的代码不足以进一步调查。
    • 嗨@DanDinu。我看过你的代码,我认为它没有多大意义:你正在等待 resultTask 在它完成的那一刻,它阻塞了之前的线程。您应该等待Task.WhenAny 真正执行异步代码。
    猜你喜欢
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多