简体   繁体   English

CancellationTokenSource.Cancel抛出ObjectDisposedException

[英]CancellationTokenSource.Cancel throws an ObjectDisposedException

I have a class that owns a CancellationTokenSource . 我有一个拥有CancellationTokenSource的类。

public class GrabboxCell : UICollectionViewCell
{
    CancellationTokenSource _tokenSource = new CancellationTokenSource ();

    // ...
}

I'm using current token to start some long-running operations. 我正在使用当前令牌来启动一些长时间运行的操作。

My object also needs to support “recycling”. 我的目标还需要支持“回收”。 Think reincarnation. 想想转世。 All long-running operations started during previous life must be cancelled. 必须取消在前一生中开始的所有长时间运行的操作。

In this case I call Cancel and Dispose on the source, and issue a new token source: 在这种情况下,我在源上调用CancelDispose ,并发出新的令牌源:

void CancelToken (bool createNew)
{
    _tokenSource.Cancel ();
    _tokenSource.Dispose ();
    _tokenSource = null;

    if (createNew) {
        _tokenSource = new CancellationTokenSource ();
    }
}

I call this method in two places: when I want the token to expire and when this class is disposed. 我在两个地方调用此方法:当我想要令牌到期时以及何时处理此类。

public override void PrepareForReuse ()
{
    CancelToken (true);
    base.PrepareForReuse ();
}

protected override void Dispose (bool disposing)
{
    CancelToken (false);
    base.Dispose (disposing);
}

Sometimes I'm getting an ObjectDisposedException when calling _tokenSource.Cancel () from my Dispose method. 有时我从Dispose方法调用_tokenSource.Cancel ()时会遇到ObjectDisposedException Documentation says: 文件说:

All public and protected members of CancellationTokenRegistration are thread-safe and may be used concurrently from multiple threads, with the exception of Dispose , which must only be used when all other operations on the CancellationTokenRegistration have completed. CancellationTokenRegistration所有公共成员和受保护成员都是线程安全的,并且可以从多个线程同时使用,但Dispose除外,它只能在CancellationTokenRegistration上的所有其他操作都已完成时使用。

I'm not sure what to do at this moment. 我不知道此刻该怎么做。 Wrap CancelToken in a lock ? lock包裹CancelToken
Where exactly does the race condition happen and how to mitigate it? 竞争状况究竟发生在哪里以及如何减轻竞争?

I know for sure that PrepareForReuse is always called on the same thread, but Dispose may be called on a different one. 我确信PrepareForReuse 总是在同一个线程上调用,但是可以在另一个线程上调用Dispose

If this is of any use, I'm running Mono and not .NET Framework but I'm pretty sure they should have the same semantics regarding cancellation tokens. 如果这是有用的,我运行Mono而不是.NET Framework,但我很确定它们应该具有与取消令牌相同的语义。

这不是很有趣,但我将CancelDispose包装成一个try-catch,它吞下了ObjectDisposedException并且从那以后就没有问题了。

The operations being thread safe (individually) does not imply that your sequence of operations are executed at once. 线程安全(单独)操作并不意味着您的操作序列一次执行。 More specifically, since PrepareForReuse could run in a different thread as Dispose , then what could happen is that this: 更具体地说,由于PrepareForReuse可以在与Dispose不同的线程中运行,那么可能发生的是:

_tokenSource.Cancel ();
_tokenSource.Dispose ();

is executed in one thread, then there is a Context Switch between threads before executing _tokenSource = null; 在一个线程中执行,然后在执行_tokenSource = null;之前在线程之间有一个Context Switch _tokenSource = null; and then another thread tries to run again the _tokenSource.Cancel () . 然后另一个线程再次尝试运行_tokenSource.Cancel () But the tokenSource was disposed of already and not regenerated, since the first thread did not reach the last block of code of the cancel: 但是tokenSource已经被处理掉而没有重新生成,因为第一个线程没有到达取消的最后一段代码:

_tokenSource = new CancellationTokenSource ();

I would not be surprised either if you get from time to time a NullPointerException , if the context switch happened just after _tokenSource = null; 如果你不时得到NullPointerException ,如果上下文切换发生在_tokenSource = null;之后,我也不会感到惊讶_tokenSource = null; instead of before as I explained (it is also possible). 而不是之前我解释过(也有可能)。

To solve this problem I would lock your Cancel method so that the threads cannot run any part of the method before the other was finished. 为了解决这个问题,我会锁定你的Cancel方法,这样线程就不能在另一个完成之前运行方法的任何部分。

Also, to protect for the NullPointerException , which can only happen if your method Dispose is called before PrepareForReuse , you can use the Null-Conditional Operator . 另外,为了保护NullPointerException ,只有在PrepareForReuse之前调用Dispose方法时才会发生这种情况,您可以使用Null-Conditional运算符

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM