【问题标题】:CancellationTokenSource.Cancel throws an ObjectDisposedExceptionCancellationTokenSource.Cancel 引发 ObjectDisposedException
【发布时间】:2013-02-02 10:58:17
【问题描述】:

我有一个拥有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);
}

有时我从我的Dispose 方法调用_tokenSource.Cancel () 时会得到一个ObjectDisposedException。文档说:

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

我现在不知道该怎么办。将 CancelToken 包裹在 lock?
竞态条件究竟发生在哪里以及如何缓解?

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

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

【问题讨论】:

  • 有趣的是,我刚刚发现了这个:SafeCancellationTokenSource
  • I need current token to expire 是什么意思?
  • @Hamlet:我希望取消_tokenSource.Token 之前返回的令牌。
  • @Hamlet 我编辑了,现在清楚了吗?

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


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

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

    _tokenSource = new CancellationTokenSource ();
    

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

    为了解决这个问题,我会锁定你的 Cancel 方法,这样线程就不能在另一个完成之前运行该方法的任何部分。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多