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 exceptionDispose
that should only be used when all other operations haveCancellationTokenRegistration
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.
source to share
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 .
source to share