CancellationTokenSource.Cancel抛出ObjectDisposedException

时间:2013-02-02 10:58:18

标签: c# xamarin.ios dispose race-condition cancellationtokensource

我有一个拥有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方法调用_tokenSource.Cancel ()时收到Dispose。文档说:

  

CancellationTokenRegistration的所有公共成员和受保护成员都是线程安全的,并且可以从多个线程同时使用,但Dispose除外,CancellationTokenRegistration只能在{{CancelToken上的所有其他操作时使用1}}已经完成。

我不知道此刻该怎么做。在lock中换行PrepareForReuse? 竞争条件究竟发生在哪里以及如何减轻竞争?

我确信Dispose 始终在同一个帖子上调用,但可以在另一个线程上调用{{1}}。

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

2 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:0)

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

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

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

_tokenSource = new CancellationTokenSource ();

如果您时不时地获得NullPointerException,如果上下文切换发生在_tokenSource = null;之后而不是我之前解释的发生,我也不会感到惊讶。

>

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

此外,为了保护NullPointerException(只有在您的方法DisposePrepareForReuse之前被调用时才能发生),您可以使用Null-Conditional Operator