19

I am using an external library that has async methods, but not CancellationToken overloads.

Now currently I am using an extension method from another StackOverflow question to add a CancellationToken:

 public async static Task HandleCancellation(this Task asyncTask, CancellationToken cancellationToken) { // Create another task that completes as soon as cancellation is requested. http://stackoverflow.com/a/18672893/1149773 TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); cancellationToken.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false); Task cancellationTask = tcs.Task; // Create a task that completes when either the async operation completes, or // cancellation is requested. Task readyTask = await Task.WhenAny(asyncTask, cancellationTask); // In case of cancellation, register a continuation to observe any unhandled exceptions // from the asynchronous operation (once it completes). In .NET 4.0, unobserved task // exceptions would terminate the process. if (readyTask == cancellationTask) asyncTask.ContinueWith(_ => asyncTask.Exception, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); await readyTask; } 

However the underlying task still executes to completion. This wouldn't be much of a problem, but sometimes the underlying task never completes and consumes 99% of my CPU.

Is there any way to "kill" the task without killing the process?

5
  • 8
    You cannot inject any cancellation handling into a task. A CancellationTokenSource.Cancel is just a request for cancellation and you should ever think about it as "Please, please, can you please cancel the operation?" and the task itself may follow your pleasant request for cancellation or not. Commented Jun 14, 2017 at 9:16
  • 1
    The Task is a Process running in its own thread, if the designers have not given you a way to soft cancel their process then the only option is to hard kill it, though as its a 3rd party library this could have unfortunate side effects, i'd find a library that allows cancellation if i were you Commented Jun 14, 2017 at 9:24
  • Only way I know of is to use old Thread stuff and call Thread.Abort() or better - have the slow 3pty piece of code being loaded into separate application domain and unload the whole application domain on timeout. This won't save you if you're calling unmanaged code from the slow 3pty piece - only way around is then separate process. Commented Jun 14, 2017 at 9:24
  • 3
    The only real way to solve this problem is to talk to developer of that external library and ask him to add cancellation support or at least fix the code to not eat 99% cpu without completion. Commented Jun 14, 2017 at 10:01
  • Related: How to cancel a Task in await? Commented Oct 18, 2023 at 6:14

4 Answers 4

23

I am using an extension method from another StackOverflow question

That code is very old.

The modern AsyncEx approach is an extension method Task.WaitAsync, which looks like this:

var ct = new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token; await myTask.WaitAsync(ct); 

I like how the API ended up because it's more clear that it's the wait that is cancelled, not the operation itself.

Is there any way to "kill" the task without killing the process?

No.

The ideal solution is to contact the authors of the library you're using and have them add support for CancellationToken.

Other than that, you're in the "cancel an uncancelable operation" scenario, which can be solved by:

  • Putting the code in a separate process, and terminating that process on cancellation. This is the only fully safe but most difficult solution.
  • Putting the code in a separate app domain, and unloading that app domain on cancellation. This is not fully safe; terminated app domains can cause process-level resource leaks.
  • Putting the code in a separate thread, and terminating that thread on cancellation. This is even less safe; terminated threads can corrupt program memory.
Sign up to request clarification or add additional context in comments.

4 Comments

How about Task.WhenAny(myTask, Task.Delay(-1, cancellationToken))? But yes, still not very nice to have the started task running to completion in the background when the token is cancelled.
@BentRasmussen: That code can cause a dangling Task.Delay. Those can add up and cause issues. WaitAsync will clean up all dangling tasks/token sources when either the task completes or the token cancels.
Task.WaitAsync(CancellationToken) is in the BCL since .NET 6 now
"This is the only fully safe but most difficult solution." -- Running the code on a separate process is safe in the sense that the current process will not get corrupted. Nevertheless the overall state of the application might still become corrupted, if we consider as state also the filesystem, local or remote database etc that the application might depend on. This state might become inconsistent or invalid after an abrupt killing of the process.
1

The only way I can think of is to change the TaskScheduler and mange the creation of the threads that are used for the tasks yourself. That is a lot of work.

The basic concept is to create your own implementation of the TaskScheduler, start a new task with your own scheduler assigned. This way you get your scheduler to be the current one and start your problematic task from this task.

There are still reason that may not work. If the task causing you trouble creates more tasks using the default task scheduler you still got the same problem. (Task.Run does so)

How ever if they are using the async/await key words your scheduler will remain active.

Now with the scheduler under your own control, you can kill any task by using Thread.Abort.

To get a idea about the implementation afford, you should have a look at the ThreadPoolTaskScheduler. That is the default implementation of the scheduler.

As I said this is a lot of work, but the only way I can think of to kill task that can't be cancelled.

To get a test running if that even works at all you may only want to implement the behaviour the ThreadPoolTaskScheduler has for the TaskCreationOptions.LongRunning option. So spawning a new thread for each task.

Comments

1

Is there any way to "kill" the task without killing the process?

No, there isn't. Quoting from a blog post by Microsoft:

So, can you cancel non-cancelable operations? No.

Even if you kill the process, there is no guarantee that the operation itself will be stopped. A Task, also known as a promise, is a general construct that represents the completion of some operation, and exposes no information about how and where this operation is performed. For all you know, this operation might be performed on a device driver, on a thread, on a separate process, on a separate machine, or on a remote server at the other side of the world. You can't inject cancellation behavior to an operation that was never designed to be canceled, more than you can inject self-destructing behavior to a flying rocket that was never equipped with self-destructing hardware.

Let's see an example:

public async Task<int> DoStuffAsync() { Process p = new(); p.StartInfo.FileName = @"C:\SomeProgram.exe"; p.Start(); await p.WaitForExitAsync(); return p.ExitCode; } 

If you launch the asynchronous DoStuffAsync() operation and then before the Task is completed you kill the current process, the SomeProgram.exe will keep running. So assuming that you don't have access to the source code of the DoStuffAsync and you don't know what it's doing internally, you won't be able to do anything to stop it after it has been launched. What you can do is ask the developer who owns it to release a new version that supports cancellation, or search for a better library.

Comments

-1

As you suggest you can cancel a task by passing in a CancellationToken and then calling Cancel.

As for how you'd go about triggering that cancellation depends on the nature of your application.

A few possible scenarios

  1. Carry on until you click cancel
  2. Cancel after a fixed time
  3. Cancel if there's been no progress for a fixed time

In case 1 you simply cancel the task from your cancel button, for example

private void cancel_Click(object sender, RoutedEventArgs e) { ... cts = new CancellationTokenSource(); await MyAsyncTask(cts.Token); cts.Cancel(); ... } 

In case 2 you could start a timer when you start your task and then cancel the task after a set time using CancelAfter, for example

private void start_Click(object sender, RoutedEventArgs e) { ... cts = new CancellationTokenSource(); cts.CancelAfter(30000); await MyAsyncTask(cts.Token); ... } 

In case 3 you could do something with progress, for example

private void start_Click(object sender, RoutedEventArgs e) { ... Progress<int> progressIndicator = new Progress<int>(ReportProgress); cts = new CancellationTokenSource(); await MyAsyncTask(progressIndicator, cts.Token); ... } void ReportProgress(int value) { // Cancel if no progress } 

Here are a few useful links Parallel programming, task cancellation, progress and cancellation, cancel tasks after set time, and cancel a list of tasks.

3 Comments

As I said in the question, in this instance I am encountering a third party library that has a method Task Start(), so no CancellationToken in order to pass.
There is another scenario which can use cancellation: a Windows Service. The service may be stopped at any time, and the service needs to stop what it's doing and return (from the "Start" call). My application is a long-running process. I want to be able to terminate it, but if it needs to monitor a cancellation token, that's not much different from passing a boolean variable into the service that is set when the service is stopped. The service monitors this flag at various points and exits methods when seen. It means the code must examine this flag in many places.
Background on a Windows Service: it stays in the "Start" method until it's finished. Then it is in a "Stopped" state. When you click "Stop" in Service Manager, the end result is to terminate the running "Start" method/process. What is needed is a way to unilaterally "kill" this start process and any other threads it may have spawned, without code to monitor a flag in every loop, method, etc.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.