How to Abort/Cancel Tpl Tasks

How do I abort/cancel TPL Tasks?

You can't. Tasks use background threads from the thread pool. Also canceling threads using the Abort method is not recommended. You may take a look at the following blog post which explains a proper way of canceling tasks using cancellation tokens. Here's an example:

class Program
{
static void Main()
{
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task.Factory.StartNew(() =>
{
while (true)
{
// do some heavy work here
Thread.Sleep(100);
if (ct.IsCancellationRequested)
{
// another thread decided to cancel
Console.WriteLine("task canceled");
break;
}
}
}, ct);

// Simulate waiting 3s for the task to complete
Thread.Sleep(3000);

// Can't wait anymore => cancel this task
ts.Cancel();
Console.ReadLine();
}
}

How to abort a Task like aborting a Thread (Thread.Abort method)?


  1. You shouldn't use Thread.Abort()
  2. Tasks can be Cancelled but not aborted.

The Thread.Abort() method is (severely) deprecated.

Both Threads and Tasks should cooperate when being stopped, otherwise you run the risk of leaving the system in a unstable/undefined state.

If you do need to run a Process and kill it from the outside, the only safe option is to run it in a separate AppDomain.


This answer is about .net 3.5 and earlier.

Thread-abort handling has been improved since then, a.o. by changing the way finally blocks work.

But Thread.Abort is still a suspect solution that you should always try to avoid.


And in .net Core (.net 5+) Thread.Abort() will now throw a PlatformNotSupportedException .

Kind of underscoring the 'deprecated' point.

Aborting a long running task in TPL

The real issue here is that the long-running call in DoWork is not cancellation-aware. If I understand correctly, what you're doing here is not really cancelling the long-running work, but merely allowing the continuation to execute and, when the work completes on the cancelled task, ignoring the result. For example, if you used the inner task pattern to call CrunchNumbers(), which takes several minutes, cancelling the outer task will allow continuation to occur, but CrunchNumbers() will continue to execute in the background until completion.

I don't think there's any real way around this other than making your long-running calls support cancellation. Often this isn't possible (they may be blocking API calls, with no API support for cancellation.) When this is the case, it's really a flaw in the API; you may check to see if there are alternate API calls that could be used to perform the operation in a way that can be cancelled. One hack approach to this is to capture a reference to the underlying Thread being used by the Task when the Task is started and then call Thread.Interrupt. This will wake up the thread from various sleep states and allow it to terminate, but in a potentially nasty way. Worst case, you can even call Thread.Abort, but that's even more problematic and not recommended.


Here is a stab at a delegate-based wrapper. It's untested, but I think it will do the trick; feel free to edit the answer if you make it work and have fixes/improvements.

public sealed class AbandonableTask
{
private readonly CancellationToken _token;
private readonly Action _beginWork;
private readonly Action _blockingWork;
private readonly Action<Task> _afterComplete;

private AbandonableTask(CancellationToken token,
Action beginWork,
Action blockingWork,
Action<Task> afterComplete)
{
if (blockingWork == null) throw new ArgumentNullException("blockingWork");

_token = token;
_beginWork = beginWork;
_blockingWork = blockingWork;
_afterComplete = afterComplete;
}

private void RunTask()
{
if (_beginWork != null)
_beginWork();

var innerTask = new Task(_blockingWork,
_token,
TaskCreationOptions.LongRunning);
innerTask.Start();

innerTask.Wait(_token);
if (innerTask.IsCompleted && _afterComplete != null)
{
_afterComplete(innerTask);
}
}

public static Task Start(CancellationToken token,
Action blockingWork,
Action beginWork = null,
Action<Task> afterComplete = null)
{
if (blockingWork == null) throw new ArgumentNullException("blockingWork");

var worker = new AbandonableTask(token, beginWork, blockingWork, afterComplete);
var outerTask = new Task(worker.RunTask, token);
outerTask.Start();
return outerTask;
}
}

How to abort or terminate a task of TPL when cancellation token is unreachable?

My previous solution was based on an optimistic assumption that the enumerable is likely to not hang and is quite fast. Thus we could sometimes sucrifice one thread of the system's thread pool? As Dax Fohl pointed out, the task will be still active even if its parent task has been killed by cancel exception. And in this regard, that could chock up the underlying ThreadPool, which is used by default task scheduler, if several collections have been frozen indefinitely.

Consequently I have refactored ToCancellable method:

public static IEnumerable<T> ToCancellable<T>(this IEnumerable<T> @this, CancellationToken token)
{
var enumerator = @this.GetEnumerator();
var state = new State();

for (; ; )
{
token.ThrowIfCancellationRequested();

var thread = new Thread(s => { ((State)s).Result = enumerator.MoveNext(); }) { IsBackground = true, Priority = ThreadPriority.Lowest };
thread.Start(state);

try
{
while (!thread.Join(10))
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
thread.Abort();
throw;
}

if (!state.Result)
yield break;

yield return enumerator.Current;
}
}

And a helping class to manage the result:

class State
{
public bool Result { get; set; }
}

It is safe to abort a detached thread.

The pain, that I see here is a thread creation which is heavy. That could be solved by using custom thread pool along with producer-consumer pattern that will be able to handle abort exceptions in order to remove broken thread from the pool.

Another problem is at Join line. What is the best pause here? Maybe that should be in user charge and shiped as a method argument.

Adding abort all tasks using a single cancellation token


  1. The task did not receive any cancellation. Its is just a state change of the token. The task has to watch the token, if there is a cancellation request then the task may cancel the operation if possible.

  2. Each task has its own token, because CancellationToken is a struct and will be copied by value. For watching the token (see 1.) the task had to keep the token.

  3. No, you have to await the tasks and you cannot force the tasks to cancel. With the Cancel operation you only send a request for cancellation and a running task may cancel or finish successfully (depends on implementation). Only not started yet tasks (waiting to run) will be canceled by the scheduler (they will not start at all)

As an example how to implement such an cancellation you are looking for here a very simple sample

The original service

public interface IFooService
{
Task DoAsync( CancellationToken cancellationToken );
}

and the one that will handle the cancellation

public interface ICancelableFooService : IFooService
{
Task CancelAsync();
}

public class CancelableFooService : ICancelableFooService
{
private readonly IFooService _foo_service;
private readonly object _sync = new object();
private List<Task> _createdtasks = new List<Task>();
private CancellationTokenSource _cts = new CancellationTokenSource();

public CancelableFooService(IFooService foo_service)
{
_foo_service = foo_service;
}

public async Task CancelAsync()
{
_cts.Cancel();
var t = Task.WhenAll( _createdtasks );
try
{
await t;
}
catch { /* we eat all exceptions here */ }
lock( _sync )
{
_cts = new CancellationTokenSource();
_createdtasks.Clear();
}
}

public Task DoAsync( CancellationToken cancellationToken )
{
lock(_sync)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource( _cts.Token, cancellationToken );
var token = cts.Token;
var task = _foo_service.DoAsync( token );
_createdtasks.Add( task );
}
return task;
}
}

Cancel / Abort Tasks

If you want to cancel a task in c#, you have to add a CancellationTokenSource that you can call to cancel the task

It would look something along the lines of

CancellationTokenSource cts = new CancellationTokenSource();
Task t = new Task(() => TaskRun(), cts.Token);
t.Start();
cts.Cancel();

In your example it would more-or-less be like:

// ...

CancellationTokenSource cts = new CancellationTokenSource();
while(true)
{
Socket clientSocket = serverSocket.Accept();
Client client = new Client(clientSocket);
Task t = new Task(() => client.Handle(), cts.Token);
t.Start();
tasks.Add(t);

if(tasks.Count() >= 10) cts.Cancel();
}

// ...

But I don't quite understand your code. You have multiple infinite loops, including an infinite loop inside of another loop. Once your code gets to the part for Socket clientSocket... it will never get out of that loop. In addition, cancelling all the tasks in your List<Task> won't remove those tasks and you'll eventually run out of memory.

If you are only hoping for 10 tasks you might be better of with a set task array like Task[] tasks = new Task[10]; and then when you fill up the array, exit out of the inner loop -- this would also allow you to use a for/foreach loop.



Related Topics



Leave a reply



Submit