Task Status Value is Faulted instead Cancelled - c#

I need to call a method in action of my Task object. My task performs some kind of reading and I cancel the operation if it takes more than 2 seconds to complete.
I have this code as simulation:
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(2000));
var task = Task.Run(() =>
{
try
{
int i = 0;
while (true)
{
Thread.Sleep(500);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("i = {0}", i);
i++;
if (i > 3) throw new InvalidOperationException();
}
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
});
task.ContinueWith(t => Console.WriteLine(t.Status), TaskContinuationOptions.NotOnRanToCompletion);
My console outout is as follows:
This is what I expect and works for me. If I copy the code inside the task and create a method I no longer get the task status as Cancelled. I get status as Faulted. I must know if the operation was cancelled or an exception happened while reading process. I cannot figure out why I do not get the task status as Cancelled here.
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(2000));
var task = Task.Run(() =>
{
try
{
Tester(cts);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
});
private static void Tester(CancellationTokenSource cts)
{
int i = 0;
while (true)
{
Thread.Sleep(500);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("i = {0}", i);
i++;
if (i > 3) throw new InvalidOperationException();
}
}

You get the expected Canceled result, if you pass the token to Task.Run:
var task = Task.Run(() =>
{
try
{
Tester(cts);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
}, cts.Token);
The task needs to know which token will throw the OperationCanceledException - only an exception from the correct source cancels the task, every other exception will just fault it.
From MSDN:
When a task instance observes an OperationCanceledException thrown by user code, it compares the exception's token to its associated token (the one that was passed to the API that created the Task). If they are the same and the token's IsCancellationRequested property returns true, the task interprets this as acknowledging cancellation and transitions to the Canceled state.
I don't fully understand why you get the Canceled result in the first place, it looks like it has to do with when the CancellationTokenSource is captured.

Related

Catching exceptions immediately does not work with Task.WhenAll

I have two instances of a class which creates a UDP socket to receive data from UDP clients. If one of the instances throws an exception I want to handle it immediately in a higher layer. In my program they're started with await Task.WhenAll(recv1.StartAsync(), recv2.StartAsync). This however waits for all tasks to finish before the first exception is thrown. Any ideas on how to resolve this problem?
static async Task Main(string[] args)
{
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint2);
var cts = new CancellationTokenSource();
try
{
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...
cts.Cancel();
}
}
class UdpReceiver
{
public UdpReceiver(IPEndPoint endpoint)
{
udpClient = new UdpClient(endpoint);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var result = await ReceiveAsync(cancellationToken);
var message = Encoding.UTF8.GetString(result.Buffer);
Trace.WriteLine($"UdpClient1 received message:{Encoding.UTF8.GetString(result.Buffer)}");
// throw new Exception("UdpClient1 raising exception");
}
}
}
private async Task<UdpReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<UdpReceiveResult>();
using (cancellationToken.Register(() => tcs.TrySetCanceled(), false))
{
var task = udpClient.ReceiveAsync();
var completedTask = await Task.WhenAny(task, tcs.Task);
var result = await completedTask.ConfigureAwait(false);
return result;
}
}
private UdpClient udpClient;
}
Update 1: Task.WhenAny would be a viable solution. Thanks #CamiloTerevinto
try
{
await await Task.WhenAny(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...
cts.Cancel();
}
Update 2: For a more fine grained exception handling of all tasks I'd go with my own adapted implementation of Task.WhenAll proposed by #Servy.
The behavior is different enough from the framework WhenAll implementation that you're probably best just writing your own adapted version, fortunately it's not particularly hard to implement. Just attach a continuation to every single task, if any is cancelled or faulted, the resulting task does the same, if it succeeds store the result, and if the last task was the one to succeed, complete the task with all of the stored results.
public static Task<IEnumerable<TResult>> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.FromResult(Enumerable.Empty<TResult>());
}
var tcs = new TaskCompletionSource<IEnumerable<TResult>>();
var results = new TResult[listOfTasks.Count];
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task<TResult> task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
results[taskIndex] = task.Result;
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(results);
}
}
});
}
return tcs.Task;
}
As with so many task based generic operations, you also need a version without the results, and if you don't want to deal with a notable overhead, you really need to just copy-paste the result-based approach but with all of the results ripped out, which isn't hard, just inelegant. Turning all of these tasks into tasks with a result would also work, but for an operation like this the overhead is likely problematic.
public static Task WhenAll(IEnumerable<Task> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>();
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(true);
}
}
});
}
return tcs.Task;
}
There's probably a way to do it but I can't think of one without making your code very messy. It'd be better to handle the exception in the actual task. If you need to handle it with common code, use a handler delegate.
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
//This is our common error handler
void HandleException(Exception ex)
{
Log("Exception!" + ex.Message);
cts.Cancel();
}
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint1);
//We pass the handler as one of the arguments
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token, HandleException), udpReceiver2.StartAsync(cts.Token, HandleException));
}
class UdpReceiver
{
public async Task StartAsync(CancellationToken cancellationToken, Action<Exception> errorHandler)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
//Main logic goes here
}
}
catch(Exception ex)
{
errorHandler(ex); //Call common error handling code
}
}
You could await the tasks in two steps. In the first step await any of them to complete, and in case of failure initiate the cancellation. Don't handle the exception in this step. Delay the exception handling until the second step, after awaiting all of the tasks to complete. Both tasks may have failed, so you may want to handle the exception of each one separately.
Task task1 = udpReceiver1.StartAsync(cts.Token);
Task task2 = udpReceiver2.StartAsync(cts.Token);
// Await any task to complete
Task firstCompletedTask = await Task.WhenAny(task1, task2);
if (firstCompletedTask.IsFaulted) cts.Cancel();
try
{
// Await them all to complete
await Task.WhenAll(task1, task2);
}
catch
{
if (task1.IsFaulted) HandleException(task1.Exception.InnerException);
if (task2.IsFaulted) HandleException(task2.Exception.InnerException);
}

How to cancel a Task using CancellationToken?

So I've this code:
//CancelationToken
CancellationTokenSource src = new CancellationTokenSource();
CancellationToken ct = src.Token;
ct.Register(() => Console.WriteLine("Abbruch des Tasks"));
//Task
Task t = new Task(() =>
{
System.Threading.Thread.Sleep(1000);
if (ct.IsCancellationRequested)
{
try
{
//Throw
ct.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
Console.WriteLine(
"ThrowIfCancellationRequested() liefert eben eine Exception");
}
}
}, ct);
//Run Task and Cancel
t.Start();
src.CancelAfter(350);
t.Wait();
// Get Information
Console.WriteLine("Canceled: {0} . Finished: {1} . Error: {2}",
t.IsCanceled, t.IsCompleted, t.IsFaulted);
So in this case I canceled my Task but my output in the end is:
"Canceled: False . Finished: True . Error: False"
In my opinion it should be "Canceled:True . Finished:False".
Why do I get this result? Because I try to catch the exception?
I've tried it without the try - catch block, but then my program stops because of the OperationCanceledException. Can somebody help me?
You're swallowing the exception, thus the task is flagged as finished as you actually handle the exception and it doesn't propagate outwards.
Instead, don't catch the exception inside the delegate, catch it outside:
void Main()
{
CancellationTokenSource src = new CancellationTokenSource();
CancellationToken ct = src.Token;
ct.Register(() => Console.WriteLine("Abbruch des Tasks"));
Task t = Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
}, ct);
src.Cancel();
try
{
t.Wait();
}
catch (AggregateException e)
{
// Don't actually use an empty catch clause, this is
// for the sake of demonstration.
}
Console.WriteLine("Canceled: {0} . Finished: {1} . Error: {2}",
t.IsCanceled, t.IsCompleted, t.IsFaulted);
}

How to make exception be awared of before calling Task.WaitAll?

In my case, I created tasks by:
IList<Task> Tasks = new List<Task>();
Tasks.Add(Task.Run(async () => { await functionAsync();}));
I need these tasks to run infinitely in order to continuously processing some incoming data. However, in cases when some fatal error/exceptions happen, I need to end the program by canceling all the tasks. I put together an simple example to simulate what I intended to do , but it does not work. I thought a task will considered to be end when throw an exception, and the WaitAny should return with AggregatedException, but it doesn't seem to be how it actually works. So, how can I make it correct?
public static void Main(string[] args)
{
Console.WriteLine(nameof(Main));
for (int i = 1; i < 5; i++)
{
var i1 = i;
_tasks.Add(Task.Run(async () => { await Tryme(i1); }));
}
try
{
Task.WaitAny(_tasks.ToArray());
}
catch (Exception e)
{
Console.WriteLine("Stop my program if any of the task in _tasks throw exception");
Console.WriteLine(e);
}
Console.ReadLine();
}
private static async Task Tryme(int i)
{
Console.WriteLine($"I'm {i}");
if (i == 3)
{
Console.WriteLine($"{i} is throwing the exception");
throw new Exception("fake one");
}
await Task.Delay(TimeSpan.MaxValue);
}
Instead of manually canceling the entire task-chain, you can use TPL Dataflow which falts the entire block for you if an unhandeled exception occurs, or can be configured to behave in other modes if needed:
var actionBlock = new ActionBlock<int>(i => TryMe(i));
foreach (var num in Enumerable.Range(0, 100))
{
actionBlock.Post(num);
}
try
{
await actionBlock.Completion();
}
catch (Exception e)
{
// Block finished prematurely, handle exception.
}
Note Dataflow takes care of parralisation for you, no need to manually create tasks.
Got some clue from this post . It looks that WaitAny will not throw any exception. I got the exception by:
int faultIndex = Task.WaitAny(_tasks.ToArray());
if (_tasks[faultIndex].IsFaulted)
{
Console.WriteLine($"{faultIndex} end");
throw _tasks[faultIndex].Exception;
}

Canceled task also appears as completed

I'm playing around with async-await and cancellation to get some more understanding on the matter. For this I have made the following console application:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
private static CancellationTokenSource _cancellationTokenSource;
private static CancellationToken _cancellationToken;
static void Main(string[] args)
{
Console.CancelKeyPress += myHandler;
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
var task = DoWorkAsync(_cancellationToken).ContinueWith(ContinueMethod);
task.Wait();
Console.ReadLine();
}
protected static void myHandler(object sender, ConsoleCancelEventArgs args)
{
if (_cancellationToken.CanBeCanceled)
{
_cancellationTokenSource.Cancel();
}
args.Cancel = true;
}
static void ContinueMethod(Task task)
{
if (task.IsCanceled)
{
Console.WriteLine("The task was canceled");
}
if (task.IsCompleted)
{
Console.WriteLine("The task completed successfully");
}
if (task.IsFaulted)
{
if (task.Exception != null)
{
var exceptions = task.Exception.Flatten().InnerExceptions;
foreach (var exception in exceptions)
{
Console.WriteLine(exception.Message);
}
}
Console.WriteLine("The task failed");
}
}
static async Task DoWorkAsync(CancellationToken cancellationToken)
{
await Task.Run(() => DoWork(cancellationToken), cancellationToken);
}
static void DoWork(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("DoWork() is started");
// Uncomment the following line of code to put the task in a 'faulted' state
//throw new Exception();
for (var count = 0; count < 10; count++)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Get a cancelation request");
cancellationToken.ThrowIfCancellationRequested();
}
else
{
Thread.Sleep(500);
Console.WriteLine("Count : " + count);
}
}
Console.WriteLine("DoWork() is finished");
}
}
}
When I let the application complete, I correctly receive the "The task completed successfully" message.
Now when I press CTRL+C, which triggers a cancel on the started task (see interception through myHandler), I correctly get the "The task was canceled" message. But I also get the "The task completed successfully" message. I was not expecting the task to also show up as complete, since I canceled it.
In case I uncomment the throw new Exception(); line in the DoWork() method, I correctly receive the "The task failed" message, but also the "The task completed successfully" message.
Am I wrong in my assumption and is this as designed? Or am I missing something else entirely?
I could off course work around this by adding an additional check as follows:
if (task.IsCompleted && !task.IsCanceled)
{
Console.WriteLine("The task completed successfully");
}
But I'm not sure if this is the correct way or if something else in my program is causing this completed state.
Thanks in advance for your input and/or clarification on this matter.
The documentation of Task.IsCompleted says
IsCompleted will return true when the task is in one of the three final states: RanToCompletion, Faulted, or Canceled.
So IsCompleted tells you at least that the Task is not running any more. It does not indicate if the Task completed successfully, failed or was cancelled.
Use Task.IsCompletedSuccessFully

Throw an aggregate exception from a task won't break all other tasks

I have two tasks:
try
{
List<Task> tasks = new List<Task>();
Task task1 = Task.Factory.StartNew(() => func(param));
tasks.Add(task1);
if (someCondition)
{
Task task2 = Task.Factory.StartNew(() => func2(param2));
tasks.Add(task2);
}
tasksArr = tasks.ToArray();
Task.WaitAll(tasksArr);
}
catch (AggregateException e)
{
//handle
}
catch (Exception)
{
//handle
}
If an exception occurs in one of the tasks, I'm throwing a new aggregation exception, but 'm reaching the catch statement only after the second task finishes. I want to catch it immediately, in order to stop unnecessary work in the second task.
I tried to use a cancellation token:
CancellationTokenSource cts = new CancellationTokenSource();
Task.WaitAll(tasksArr,cts.Token);
But this wasn't a good solution since it requires that the inner exception will be handled + the caught exception will be an OperationCanceledException
Any ideas?
To cancel waiting it is necessary to pass an instance of CancellationTokenSource class to Task.WaitAll Method (Task[], CancellationToken). Also, use a continuation on each task that runs when a task fails (faults, i.e. TaskStatus.Faulted).
var cancellationTokenSource = new CancellationTokenSource();
var tasks = new List<Task>();
var firstTask = new Task(FirstMethod);
firstTask.ContinueWith(t =>
{
Console.WriteLine("First task exception: {0}", t.Exception);
cancellationTokenSource.Cancel();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
tasks.Add(firstTask);
firstTask.Start();
var secondTask = new Task(SecondMethod);
secondTask.ContinueWith(t =>
{
Console.WriteLine("Second task exception: {0}", t.Exception);
cancellationTokenSource.Cancel();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
tasks.Add(secondTask);
secondTask.Start();
try
{
var cancellationToken = cancellationTokenSource.Token;
var tasksArray = tasks.ToArray();
Task.WaitAll(tasksArray, cancellationToken);
}
catch (Exception e)
{
// ...
}

Categories

Resources