Wait for a canceled task to complete before continuing execution - c#

I have the following code, where a Task can be canceled, but I basically need to wait for it to complete (to ensure integrity) before throwing the OperationCanceledException to the caller.
public static void TaskCancellationTest() {
try {
Console.WriteLine("TaskCancellationTest started.");
var cts = new CancellationTokenSource();
var t = Task.Run(() => {
if (cts.Token.IsCancellationRequested) return;
Console.WriteLine("1");
Task.Delay(2000).Wait();
Console.WriteLine("2");
}).ContinueWith(task => {
if (cts.Token.IsCancellationRequested) return;
Console.WriteLine("3");
Task.Delay(2000).Wait();
Console.WriteLine("4");
});
Task.Run(() => {
Task.Delay(1000).Wait();
Console.WriteLine("Cancelling...");
cts.Cancel();
});
t.Wait();
try {
cts.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException) {
Console.WriteLine("Gracefully canceled.");
}
Console.WriteLine("TaskCancellationTest completed.");
}
catch (Exception ex) {
Console.WriteLine("TaskCancellationTest... Failure: " + ex);
}
}
The result, as expected, is:
1
Cancelling...
2
Gracefully canceled.
It works, but I would prefer to pass the CancellationToken to the methods as I understand this is a better pattern. I would also like to be able to observe the token inside the method body and to call ThrowIfCancellationRequested() to abort without having to wait for the next ContinueWith().
I was playing with the following alternative code, which also works, but is there any way to have an OperationCanceledException raised instead of an AggregateException?
If I pass the cancellationToken to the WaitAll() method, the problem is that it will throw an OperationCanceledException immediately upon cancellation of the token, rather than waiting for the tasks t1 and t2 to actually complete (they will continue running in the background) and then only throwing the exception.
public static void TaskCancellationTest2() {
try {
Console.WriteLine("TaskCancellationTest2 started.");
var cts = new CancellationTokenSource();
var t1 = Task.Run(() => {
Console.WriteLine("1");
Task.Delay(2000).Wait();
Console.WriteLine("2");
}, cts.Token);
var t2 = t1.ContinueWith(task => {
Console.WriteLine("3");
Task.Delay(2000).Wait();
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("4");
}, cts.Token);
Task.Run(() => {
Task.Delay(1000).Wait();
Console.WriteLine("Cancelling...");
cts.Cancel();
});
try {
try {
Task.WaitAll(t1, t2);
}
catch (AggregateException ae) {
if (ae.InnerExceptions.Count == 1 && ae.InnerExceptions.Single() is OperationCanceledException) {
throw ae.InnerExceptions.Single();
}
throw;
}
}
catch (OperationCanceledException) {
Console.WriteLine("Gracefully canceled.");
}
Console.WriteLine("TaskCancellationTest2 completed.");
}
catch (Exception ex) {
Console.WriteLine("TaskCancellationTest2... Failure: " + ex);
}
}
I have prepared a fiddle here.
This question's title is very similar to mine, but the accepted answer is unfortunately not relevant to my case.
Do you know of any way to achieve what I would like, that makes as good use of CancellationToken as possible?

I think the TPL is designed to eagerly complete tasks if the CancellationToken is set. Part of the reason you are seeing this behavior is because you are calling t.Wait(cts.Token). The overload that takes a CancellationToken will stop waiting if the token is set even if the task hasn't ran to completion.
It's the same with ContinueWith if you pass in a CancellationToken the task can complete as soon as that token is set.
Change your code to call t.Wait() and ContinueWith without a token and you'll get the behavior you want.
public static void TaskCancellationTestNotWorking1()
{
try
{
Console.WriteLine("TaskCancellationTestNotWorking started.");
var cts = new CancellationTokenSource();
var t = Task.Run(() =>
{
Console.WriteLine("1");
Thread.Sleep(2000);
Console.WriteLine("2");
}, cts.Token).ContinueWith(task =>
{
Console.WriteLine("3");
Thread.Sleep(2000);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("4");
});
Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Cancelling...");
cts.Cancel();
}, cts.Token);
try
{
t.Wait();
}
catch (OperationCanceledException)
{
Console.WriteLine("IsCanceled " + t.IsCanceled);
Console.WriteLine("IsCompleted " + t.IsCompleted);
Console.WriteLine("Gracefully canceled.");
}
catch (AggregateException)
{
Console.WriteLine("IsCanceled " + t.IsCanceled);
Console.WriteLine("IsCompleted " + t.IsCompleted);
Console.WriteLine("Gracefully canceled 1.");
}
Console.WriteLine("TaskCancellationTestNotWorking completed.");
}
catch (Exception ex)
{
Console.WriteLine("TaskCancellationTestNotWorking... Failure: " + ex);
}
}
You might find this article useful How do I cancel non-cancelable async operations?

Related

How to stop async Task after a certain period of time

I am trying to stop a task in C# after a certain period of time.
For my code: the Task.Delay().Wait() should just represent some work that the Task does.
My Code:
public static void Main()
{
Console.WriteLine("starting app");
try
{
Console.WriteLine("before");
DoStuff(1000);
Console.WriteLine("after");
}
catch
{
Console.WriteLine("TIMEOUT");
}
Console.WriteLine("Main finished wait 5 sec now");
Task.Delay(10000).Wait();
Console.WriteLine("Closing app now");
}
public static async Task DoStuff(int time)
{
Task real = Task.Run(()=>
{
Task.Delay(2000).Wait();
Console.WriteLine("Task Running");
Task.Delay(2000).Wait();
Console.WriteLine("still running");
Task.Delay(2000).Wait();
Console.WriteLine("Not dead yet");
Task.Delay(1000).Wait();
Console.WriteLine("Task done");
Task.Delay(5000);
});
bool success = real.Wait(time);
if ( success )
{
Console.WriteLine("Task finished in time");
await real;
}
else
{
Console.WriteLine("Task did not finish");
real.Dispose();
throw new TimeoutException("The task took too long");
}
}
I already tried it with the cancellation token, but I do not have a loop to check the token every iteration. So I tried to do it with the Task.Wait(time) and I get the right message, but the task does not stop after using the Task.Dispose() method. I get the following output:
So I get the current output but the task continues to run in the back.. Any ideas on how to solve this?
Firstly: never Wait() on tasks (unless you know they have already finished); use await. As for the timeout: CancellationTokenSource can be made to time out after a delay, as below.
Note that cancellation-tokens do not interrupt code - you (or other code, as in the case of Task.Delay) need to either check for cancellation (for example, ThrowIfCancellationRequested()), or you need to use Register(...) to add a callback that will be invoked when it is cancelled.
using System;
using System.Threading;
using System.Threading.Tasks;
static class P
{
public static async Task Main()
{
Console.WriteLine("starting app");
try
{
Console.WriteLine("before");
await DoStuffAsync(1000);
Console.WriteLine("after");
}
catch
{
Console.WriteLine("TIMEOUT");
}
Console.WriteLine("Main finished wait 5 sec now");
await Task.Delay(5000);
Console.WriteLine("Closing app now");
}
public static async Task DoStuffAsync(int time)
{
using var cts = new CancellationTokenSource(time); // assuming here that "time" is milliseconds
Task real = Task.Run(async () =>
{
await Task.Delay(2000, cts.Token);
Console.WriteLine("Task Running");
await Task.Delay(2000, cts.Token);
Console.WriteLine("still running");
await Task.Delay(2000, cts.Token);
Console.WriteLine("Not dead yet");
await Task.Delay(2000, cts.Token);
Console.WriteLine("Task done");
await Task.Delay(2000, cts.Token);
});
bool success;
try
{
await real;
success = true;
}
catch (OperationCanceledException)
{
success = false;
}
if (success)
{
Console.WriteLine("Task finished in time");
}
else
{
Console.WriteLine("Task tool too long");
}
}
}
Note that you can also pass a cancellation-token into Task.Run, but that simply gets checked before executing the callback - and there isn't usually a significant delay on that, so... it isn't usually worth it.
Additional note: if you want to be sure exactly what was cancelled (i.e. is the OperationCanceledException coming from cts):
catch (OperationCanceledException cancel) when (cancel.CancellationToken == cts.Token)

c# indefinte Task continue execution on exception

I have started to explore TPL in .NET to implement a windows service which will run multiple independent methods in parallel with some delay after each successful execution, following is a rough console application I came up with after looking at various examples:
class Program
{
static void Main(string[] args)
{
CancellationTokenSource _ct1 = new CancellationTokenSource();
CancellationTokenSource _ct2 = new CancellationTokenSource();
int count = 0;
var task1 = new Task(async () =>
{
try
{
while (true)
{
DoWork();
await Task.Delay(2000, _ct1.Token);
count++;
if (count >= 5)
{
throw new NotImplementedException();
}
}
}
catch (TaskCanceledException ex)
{
//Log cancellation and do not continue execution
Console.WriteLine("DoWork cancelled: " + ex.Message);
}
catch (Exception ex)
{
//Log error and continue with execution
Console.WriteLine("Error occurred at DoWork");
}
}, _ct1.Token, TaskCreationOptions.LongRunning);
var task2 = new Task(async () =>
{
try
{
while (true)
{
DoWork2();
await Task.Delay(2000, _ct2.Token);
count++;
if (count >= 5)
{
_ct2.Cancel();
}
}
}
catch (TaskCanceledException ex)
{
//Log cancellation and do not continue execution
Console.WriteLine("DoWork2 cancelled: " + ex.Message);
}
catch (Exception ex)
{
//Log error and continue with execution
Console.WriteLine("Error occurred at DoWork");
}
}, _ct2.Token, TaskCreationOptions.LongRunning);
task1.Start();
task2.Start();
Console.ReadKey();
}
public static void DoWork()
{
Console.WriteLine("Doing something...");
}
public static void DoWork2()
{
Console.WriteLine("Doing something else...");
}
}
In the above code when any exception occurs during the task execution, I need to log the error and then continue with the task execution, right now the task stops executing if there is an exception. My questions:
How to handle exceptions properly so that the task execution doesn't stop?
When I add a debugger break point at DoWork() method, the DoWork2() doesn't run which mean the tasks are not running in parallel on separate threads and running on a single thread and blocking each other. How to make sure the Tasks are running independent of each other on separate threads?
PS: The above code is a simple console app just to understand the workings of TPL, so please ignore if there are obvious design problems.
I believe that the simplest chage you can do to make your task1 running after exception is as follows:
var task1 = new Task(async () =>
{
while (true)
{
try
{
DoWork();
await Task.Delay(2000, _ct1.Token);
count++;
if (count >= 5)
{
throw new NotImplementedException();
}
}
catch (TaskCanceledException ex)
{
//Log cancellation and do not continue execution
Console.WriteLine("DoWork cancelled: " + ex.Message);
break;
}
catch (Exception ex)
{
//Log error and continue with execution
Console.WriteLine("Error occurred at DoWork");
count = 0; // without this you'll have exception thrown on each of further iterations
}
}
}, _ct1.Token, TaskCreationOptions.LongRunning);
Meaning that try...catch is moved inside the loop.
Please also note #pull420 comment on while you don't see task2 running

how to cancel Task from another Task?

try{
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task.Run(()=>
{
//DoSomething(); excute long time
}, ct);
Task.Run(()=>
{
Thread.Sleep(1000);
cts.Cancel();
}, ct).Wait();
}
catch (OperationCanceledException ex)
{
Console.WriteLine("exception" + ex.Message);
}
finally
{
Console.WriteLine("finally");
}
When I call cts.Cancel()
DoSomething still Work.....................
how can i stop first Task?
if DoSomething Has a loop
I can add ct.ThrowIfCancellationRequested() , it's working
but DoSomething not a loop , what can i do ?
Whether or not DoSomething() is a loop, it must explicitly check and respond to the IsCancellationRequested property on the cancellation Token. If this property is true, the function must return as soon as practical, even if it means not completing the full execution. Note that DoSomething() is not a loop.
void DoSomething(System.Threading.CancellationToken tok)
{
Thread.Sleep(900);
if (tok.IsCancellationRequested)
return;
Console.WriteLine("after 1");
Thread.Sleep(1800);
if (tok.IsCancellationRequested)
return;
Console.WriteLine("after 2");
}
void Main()
{
try
{
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
System.Threading.Tasks.Task.Run(() =>
{
DoSomething(ct);
//DoSomething(); excute long time
});
System.Threading.Tasks.Task.Run(() =>
{
Thread.Sleep(1000);
cts.Cancel();
}).Wait();
}
catch (OperationCanceledException ex)
{
Console.WriteLine("exception" + ex.Message);
}
finally
{
Console.WriteLine("finally");
}
}
Note: DoSomething() must reference the cancellation token and explicitly check the IsCancellationRequested property. The role of the cancellation token in the Task.Run() is explained in this answer: https://stackoverflow.com/a/3713113/41410, but it doesn't play a role is cancelling the flow of DoSomething()

Exception not caught in Task.Run

I'm trying to catch an exception thrown by a method (GetMoreCodes) run by a Task, but when debugging the exception is never handled and the catch block is never hit. Tried different techniques (in particular with/without await). This code is in an (async) button event handler.
try
{
// Task.Run(() => GetMoreCodes(CodeBufferMaxSize));
// await Task.Run(() => GetMoreCodes(0));
await Task.Run(() => { throw new Exception("test!"); });
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: " + ex);
}
This looks to me like most examples I saw here and in blogs, in particular in this one (http://blog.stephencleary.com/ - big thanks #stephen-cleary).
For now, I only want the application not to crash and log an error if any.
Am I missing something?
await Task.Run(() => {
try
{
throw new Exception("test!");
}
catch (Exception ex)
{
Console.WriteLine("Catched");
}
});
Why not just catched in the Task.Run() ?
If you want to catch it in Console.Application with your code you can do it like this. But t.Wait() is blocking the UI Thread and waits for the Task to finish.
public static void Main(string[] args)
{
try
{
Task t = TestError();
t.Wait();
}
catch (Exception ex)
{
Console.WriteLine("Catched");
}
Console.ReadLine();
}
public static async Task TestError()
{
// Task.Run(() => GetMoreCodes(CodeBufferMaxSize));
// await Task.Run(() => GetMoreCodes(0));
await Task.Run(() => { throw new Exception("test!"); });
}

Rethrow exception with async task?

I have this code:
try
{
await Task.Run(() =>
{
token.ThrowIfCancellationRequested();
//Call WebApi...
}, token);
}
catch (OperationCanceledException oex)
{ }
catch
{
throw;
}
The method within await either returns code 200 or throws a particular exception that I want to evaluate.
As the action is cancelable, an OperationCanceledException might be thrown and I just want to ignore it.
The above should work. However is it possible to consolidate both catch statements with the new C# 6.0 syntax by using when?
You don't need C# 6, just get rid of the last catch block and have only the OperationCanceledException
try
{
await Task.Run(() =>
{
token.ThrowIfCancellationRequested();
//Call WebApi...
}, token);
}
catch (OperationCanceledException oex)
{ }
The one thing you might want to do with C# 6 is check that the token that was used to raise the excption is the token you passed in
try
{
await Task.Run(() =>
{
token.ThrowIfCancellationRequested();
//Call WebApi...
}, token);
}
catch (OperationCanceledException oex) when (oex.CancellationToken == token)
{ }
Or using the old style
try
{
await Task.Run(() =>
{
token.ThrowIfCancellationRequested();
//Call WebApi...
}, token);
}
catch (OperationCanceledException oex)
{
if(oex.CancellationToken != token)
throw;
}

Categories