7
\$\begingroup\$

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.

\$\endgroup\$
6
  • \$\begingroup\$ Rolled back due to answer invalidation \$\endgroup\$ Commented Jan 24, 2016 at 8:54
  • \$\begingroup\$ ok but to any new viewers: I'm still looking for input \$\endgroup\$ Commented Jan 24, 2016 at 9:17
  • \$\begingroup\$ You said yourself Calling ct.Cancel() stops the operation even if it's something that cannot usually be interrupted.. Furthermore with that cancellation mechanism you have control because it is your responsability to call ThrowIfCancellationRequested on the cancellable method, while with abortion you don't, solving all safety concerns. What would you like to have addressed? \$\endgroup\$ Commented Jan 24, 2016 at 9:29
  • \$\begingroup\$ The code looks a bit complicated. Maybe there isn't a native thing for it, but isn't there a simpler way to do essentially the same? \$\endgroup\$ Commented Jan 24, 2016 at 10:28
  • 2
    \$\begingroup\$ Thread.Abort is evil (Google that). Totally unsafe. You might abort the static ctor of string and therefore make the entire string class permanently unavailable. \$\endgroup\$
    – usr
    Commented Jan 24, 2016 at 14:18

2 Answers 2

7
\$\begingroup\$

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 ctcould be easily replaced by at least token or if you want to go a step further cancelToken. And t could be simply threador 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.

\$\endgroup\$
2
  • \$\begingroup\$ Sorry I messed up copying the working code. cts is actually instantiated outside action scope. It's also stored in a variable and not just forgotten like it may seem in the template. \$\endgroup\$ Commented Jan 24, 2016 at 8:44
  • 1
    \$\begingroup\$ @user1306322 That kinda invalidates the answer which is not a good thing for Code Review. Even though it's just 1 line moved up in this scenario makes all the difference. Basically all the text on code review no longer applies. \$\endgroup\$ Commented Jan 24, 2016 at 8:47
2
\$\begingroup\$
  1. Declare cancellation token and Cancellation token sources:

    private CancellationTokenSource _tokenSource = null;
    private CancellationToken _token;
    private Task _executionTask = null;
    
  2. During initialization of the class assign token:

      _tokenSource = new CancellationTokenSource();
      _token = _tokenSource.Token;
    
  3. During the task initialization pass the token and specify TaskCreationOption.LongRunning:

    _executionTask = Task.Factory.StartNew(this.LongRunningTask, _token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    
  4. 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
    
        }
    }
    
  5. 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;
            }
    
        }
    }
    
  6. 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;
        }
    }
    
\$\endgroup\$
4
  • 2
    \$\begingroup\$ I think it would make more sense to not just declare the token and task separately, but to make some InstantlyCancellableTask class with public methods so the entire thing is more convenient. Also, as of April 2018, isn't there a good way to do this already? \$\endgroup\$ Commented Apr 9, 2018 at 9:26
  • \$\begingroup\$ By declaring token and task separately it is easy to expose a method to cancel the task and also to dispose them properly \$\endgroup\$ Commented Apr 10, 2018 at 3:51
  • \$\begingroup\$ afaik it's customary to make it the containing class' responsibility to manage disposal of its objects, and not do it from the outside code \$\endgroup\$ Commented Apr 10, 2018 at 6:20
  • \$\begingroup\$ That is the design choice.. we have to take.. \$\endgroup\$ Commented Apr 10, 2018 at 7:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.