4

我有一个拥有CancellationTokenSource.

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

    // ...
}

我正在使用当前令牌开始一些长时间运行的操作。

我的对象还需要支持“回收”。想想轮回。必须取消在前一个生命周期中开始的所有长时间运行的操作。

在这种情况下,我调用CancelDispose源,并发出一个新的令牌源:

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

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

我在两个地方调用这个方法:当我希望令牌过期时和当这个类被释放时。

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

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

有时我会从我的方法中ObjectDisposedException调用 when 。文档说:_tokenSource.Cancel ()Dispose

的所有公共和受保护成员CancellationTokenRegistration都是线程安全的,并且可以从多个线程同时使用,除了Dispose,它只能在所有其他操作CancellationTokenRegistration都完成时使用。

我现在不知道该怎么办。包裹CancelToken在一个lock
竞争条件到底发生在哪里以及如何减轻它?

我确定它PrepareForReuse总是同一个线程上Dispose调用,但可能会在不同的线程上调用。

如果这有任何用处,我正在运行 Mono 而不是 .NET Framework,但我很确定它们在取消令牌方面应该具有相同的语义。

4

2 回答 2

6

这不是很有趣,但我包裹Cancel并进入了一个可以吞下并且从那以后没有问题Dispose的尝试捕获。ObjectDisposedException

于 2013-02-08T19:55:52.173 回答
1

线程安全(单独)的操作并不意味着您的操作序列会立即执行。更具体地说,由于PrepareForReuse可以在不同的线程中运行Dispose,那么可能发生的情况是:

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

在一个线程中执行,然后在执行之前在线程之间进行上下文切换_tokenSource = null;,然后另一个线程尝试再次运行_tokenSource.Cancel (). 但是 tokenSource 已经被处理掉并且没有重新生成,因为第一个线程没有到达取消的最后一个代码块:

_tokenSource = new CancellationTokenSource ();

如果你不时得到 a ,我也不会感到惊讶NullPointerException,如果上下文切换发生在_tokenSource = null;我解释的之后而不是之前(这也是可能的)。

为了解决这个问题,我将锁定您的Cancel方法,以便线程在另一个完成之前无法运行该方法的任何部分。

此外,为了保护 ,NullPointerException只有在您的方法Dispose之前调用时才会发生这种情况PrepareForReuse,您可以使用Null-Conditional Operator

于 2019-07-08T07:53:31.063 回答