6
\$\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\$
  • \$\begingroup\$ Rolled back due to answer invalidation \$\endgroup\$ – Bruno Costa Jan 24 '16 at 8:54
  • \$\begingroup\$ ok but to any new viewers: I'm still looking for input \$\endgroup\$ – user1306322 Jan 24 '16 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\$ – Bruno Costa Jan 24 '16 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\$ – user1306322 Jan 24 '16 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 Jan 24 '16 at 14:18
6
\$\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.

| improve this answer | |
\$\endgroup\$
  • \$\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\$ – user1306322 Jan 24 '16 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\$ – Bruno Costa Jan 24 '16 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;
        }
    }
    
| improve this answer | |
\$\endgroup\$
  • 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\$ – user1306322 Apr 9 '18 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\$ – vishnu vardhan Apr 10 '18 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\$ – user1306322 Apr 10 '18 at 6:20
  • \$\begingroup\$ That is the design choice.. we have to take.. \$\endgroup\$ – vishnu vardhan Apr 10 '18 at 7:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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