Problem
I’m looking for a better way to make a background operation with abort/cancel support. Right now I use this approach:
Task.Factory.StartNew(() => {
var cts = new System.Threading.CancellationTokenSource();
var ct = cts.Token;
try
{
var t = System.Threading.Thread.CurrentThread;
using (ct.Register(t.Abort))
{
LongOperation();
}
}
catch {...} // an exception gets thrown about aborting threads
});
Calling ct.Cancel()
stops the operation even if it’s something that cannot usually be interrupted. The focus is that it can be stopped instantly.
I don’t think this is unsafe if I’m not doing any file writing. So far it’s been working well enough for me, but this code doesn’t look proper to me. Is there a more canonical way to start background operations and abort them instantly? Are there any safety concerns I have been lucky not to encounter so far?
Update 2 years later: I looked it all up again, and it seems like a better way to avoid problems is to “abort” a process instead of aborting a thread on your app’s main process. See this answer by Eric Lippert. to “What is a safe way to stop a running thread?” question.
Solution
Disclaimer: Don’t do that
Aborting threads usually means that the thread will terminate and will no longer be available to do anything else. While that maybe exactly what you want there are some scenarios where you have to be really careful about aborting threads namely a shutdown process i.e (where you have to do some work before exiting).
It is also worth noting that you are trying to abort a thread from the thread pool, because the TPL uses the thread pool. And those threads should be always ready to get work done. If you abort one of the threads in the worst case then the thread pool will need to create another thread to replace it, meaning that you will have a cycle of threading creation and abortion.
Code Review
You are creating a CancellationTokenSource
inside the working thread, meaning that only the working thread will be able to cancel/abort itself.
Cancellation is a mechanism meant to be used across multiple threads, because a thread is already responsible for it’s own life-cycle.
So you should use other kinds of mechanisms, i.e time-based using Stopwatch, to determine if you want to leave or not.
If you want to know how to effectively cancel an operation please head to my other answer here
EDIT
Unlike you, I don’t think there is something overly complicated on your code.
There should be a minimal set of comprehensible code to achieve some functionality and .NET API AFAIK doesn’t have a simpler way to do it. However I would like to point two small things:
You could have used a using
statement for System.Threading
You could have used meaningful names. I won’t complain too much about cts
because sometimes I also use that but I acknowledge that cancelSource
is better. However ct
could be easily replaced by at least token
or if you want to go a step further cancelToken
. And t
could be simply thread
or yet again currentThread
.
Another alternative could also be to don’t have that many variables I wouldn’t mind to write cancelSource.Token
and Thread.CurrentThread
, if they happen to be used just once.
-
Declare cancellation token and Cancellation token sources:
private CancellationTokenSource _tokenSource = null; private CancellationToken _token; private Task _executionTask = null;
-
During initialization of the class assign token:
_tokenSource = new CancellationTokenSource(); _token = _tokenSource.Token;
-
During the task initialization pass the token and specify
TaskCreationOption.LongRunning
:_executionTask = Task.Factory.StartNew(this.LongRunningTask, _token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
-
Throw the exception, when the task is cancelled or use step 5 to suppress the exception depending on use case:
private void LongRunningTask() { while (true) { if (_token.IsCancellationRequested) { _token.ThrowIfCancellationRequested(); } // do long running job here } }
-
Suppress the exception if required, depends on the business use case:
private void LongRunningTask() { while (true) { try { if (_token.IsCancellationRequested) { _token.ThrowIfCancellationRequested(); } // do long running job here } catch(OperationCanceledException ex) { // supress the exception break; } } }
-
Dispose the cancellation token and task properly.
protected virtual void Dispose(bool disposing) { try { if (!disposedValue) { if (disposing) { _tokenSource.Cancel(); _executionTask.Wait(); _tokenSource.Dispose(); _executionTask.Dispose(); } disposedValue = true; } } catch { } finally { _tokenSource = null; _executionTask = null; } }