CancellationTokenSource.Cancel throws ObjectDisposedException

I have a class that owns CancellationTokenSource

.

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

    // ...
}

      

I am using the current token to start some long running operations.

My goal should also be to support "recycling". Consider reincarnation. All lengthy surgeries started in a previous life should be canceled.

In this case, I call Cancel

both Dispose

in source and issues a new token source:

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 located.

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

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

      

Sometimes I get ObjectDisposedException

when called _tokenSource.Cancel ()

from my method Dispose

. The documentation says:

All public and protected members CancellationTokenRegistration

are thread safe and can be used concurrently from multiple threads, with the exception Dispose

that should only be used when all other operations have CancellationTokenRegistration

completed.

I'm not sure what to do at this point. Wrap CancelToken

in lock

?
Where exactly does the race condition occur and how to mitigate it?

I know for sure that it is PrepareForReuse

always called on one thread, but Dispose

can be called on another.

If helpful, I am running Mono, not the .NET Framework, but I am pretty sure they should have the same semantics regarding cancellation tokens.

+3


source to share


2 answers


It's not very interesting, but I wrapped Cancel

it Dispose

in a try-catch as well, which swallows ObjectDisposedException

and hasn't had any problems since then.



+5


source


Operations that are thread safe (individually) do not imply that your workflow is executed right away. More specifically, since it PrepareForReuse

can run in a different thread like Dispose

, the following can happen:

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

      

runs on one thread, then context switches between threads before execution _tokenSource = null;

and then another thread tries to start again _tokenSource.Cancel()

. But the tokenSource was already deleted and not regenerated because the first thread did not reach the last block of the undo code:

_tokenSource = new CancellationTokenSource ();

      



I also wouldn't be surprised if you get from time to time NullPointerException

if the context switch happens immediately after _tokenSource = null;

and not before I explained (this is also possible).

To solve this problem, I would block your method Cancel

so that threads cannot execute any part of the method until another has completed.

Also, to guard against NullPointerException

which can only happen if your method Dispose

is called before PrepareForReuse

, you can use a PrepareForReuse

conditional operator .

0


source







All Articles