C# Task.WhenAll handling Exceptions - c#

I need help with a simple Task.WhenAll C# code. I have upto 50 different tasks being fired at the same time, however, some of these calls could return an error message.
I am trying to write the exception handling code, so that I can process the ones that worked (which it does), but capture the ones that errored, so I can perform additional code against them.
There is AggregateException, but is there a way to see which call/inputs that created that exception?
I'm unable to share the actual code due to strict company policy, but an example is as follows:
List<ListDetails> libs = getListDetails();
var tasks = new Task<List<ItemDetails>>[libs.Count];
for (int i = 0; i < libs.Count; i++)
{
tasks[i] = getListItems(libs[i].ServerRelativeUrl, libs[i].ListId);
}
try
{
await Task.WhenAll(tasks);
}
catch(AggregateException aex)
{
//Capture which Server RelativeUrls and ListIDs that failed.
}

You can query the original tasks after waiting:
var myTasks = ...;
try {
await Task.WhenAll(myTasks);
}
catch (AggregateException) {
//swallow everything
}
foreach (var task in myTasks) {
if (myTask.Status == RanToCompletion)
...
}

You should check the InnerExceptions property, or you can use AggregateException.Flatten method as shown in the docs:
try {
task1.Wait();
}
catch (AggregateException ae) {
foreach (var e in ae.Flatten().InnerExceptions) {
if (e is CustomException) {
Console.WriteLine(e.Message);
}
else {
throw;
}
}
}
This will give you all the exceptions that are thrown by the child tasks but unfortunately there is no way to know which task threw a particular exception. You will need to investigate the stack traces.

You should make a array of async tasks then when you are going to add each task to array you should place a anonymous function with lambda expression in ContinueWith(){}. Here is an example :
var tasks = new List<Task>();
tasks.add(t.ContinueWith(o =>
{
if(o.IsFaulted){
Console.WriteLine(o.Exception?.InnerException);
return;
}
Console.WriteLine(o.Result);
}));
tasks.WhenAll().ContinueWith(o=>{
Console.WriteLine("All Done!")
});

Related

Task WhenAll exception

I have to execute about 10 tasks and then obtain their results (all different). I have created a wrapper to catch the exceptions, I don't want to know which one failed, but if a failure it should not continue. I also use WhenAll for this, but the problem is that then I get the results of each one and I get an unhandled exception, is there no way not to continue if there are any exceptions from my wrapper?
var 1Task = api.1TaskAsync();
var 2Task = api.2TaskAsync();
var 3Task = api.3TaskAsync();
await taskService.RunSafeAsync(() => Task.WhenAll(1Task, 2Task, 3Task));
var result = await 1Task; <-------------exception
TaskService:
public async Task RunSafeAsync(Func<Task> task)
{
try
{
await task();
}
catch (Exception ex)
{
}
}
You can't retrieve the result from a function that has faulted. It doesn't have a result-- because it never finished. Attempting to retrieve its result (as you are) is going to throw an exception, because there is nothing to assign to the variable that is supposed to accept the return value.
To avoid this, check if the task is faulted before attempting to retrieve the result.
var 1Task = api.1TaskAsync();
var 2Task = api.2TaskAsync();
var 3Task = api.3TaskAsync();
await taskService.RunSafeAsync(() => Task.WhenAll(1Task, 2Task, 3Task));
if (!1Task.IsFaulted)
{
var result = await 1Task;
If you just want to not continue if there was an exception in any of the methods then don't wrap WhenAll and react if it throws.
try
{
await Task.WhenAll(...);
// None of the tasks threw an exception;
}
catch (Exception ex)
{
// One or more tasks threw an exception
return / throw / etc.
}
//Continue the happy path
Cancellation token
If you need to attempt tasks simultaneously and abort all tasks if one fails than you'll need to look at the cancellation token and activate the token if any exceptions occur (not in the catch for whole WhenAll, but each tasks would have to be executed in a try..catch).
Maybe sequentially?
BTW. With .WhenAll all tasks will be attempted. Maybe you actually want to execute the tasks in sequence and continue only if there are not exceptions:
try
{
await F1();
await F2();
}
catch(Exception ex)
{
}
This doesn't seem slick but it makes it very clear that task 2 will not run unless task 1 is a success.

Task.Run() & Task.Delay() terminated after sometime

I have created windows service in c# and in that service, I have created 4 threads and running them the background every after 10 sec.
Below is the code:
var ThreadSize = 4;
for (int i = 0; i < ThreadSize; i++)
{
Task.Run(async () =>
{
while (1 == 1)
{
try
{
//Logic
await Task.Delay(10000, cancelSource.Token);
}
catch (Exception ex)
{
//Log the exception
}
}
});
}
The for loop will be executed only once and will create 4 threads. I am using Task.Delay to wait the thread for 10 sec and then again executing my logic. It will go and execute my logic every 10 sec.
The code is working fine, but after some time, my all threads getting terminated (Not working). I mean, the code in the logic is not working after couples of hours.
There is no exception at all.
Can any one suggested what went wrong.
Thanks you in advance.
Edited Code:
CancellationTokenSource cancelSource;
protected override void OnStart(string[] args)
{
cancelSource = new CancellationTokenSource();
Process.StartProcess(cancelSource);
}
protected override void OnStop()
{
cancelSource.Cancel();
}
public static void StartProcess(CancellationTokenSource cancelSource)
{
var ThreadSize = 4;
for (int i = 0; i < ThreadSize; i++)
{
Task.Run(async () =>
{
while (1 == 1)
{
try
{
//Logic
await Task.Delay(10000, cancelSource.Token);
}
catch (Exception ex)
{
//Log the exception
}
}
});
}
}
If any exception occurs within the Task.Run, it will be saved and thrown when the task is awaited. You're not awaiting the task, so any exception that has occurred won't be visible.
You should await the Task.Run by either using the await keyword or call .Wait() on it.
As you're spawning multiple tasks, you could add all of them to a list and then call await Task.WhenAny(tasks) which will return when any of the tasks finishes, so you can act accordingly.
Read this article for more information
Your main problem is in
catch (Exception ex)
{
throw;
}
This effectively means that you don't catch any errors. You might as well remove the try/catch for the same effect, or lack of effect.
The main structure of your Service looks OK, this won't stop by itself. The choice of Task vs Thread is not too important.
Your error happens inside //Logic and is not handled.
You will need some form of logging to find out.

How to catch CancellationToken.ThrowIfCancellationRequested

When this section of code is executed
cancellationToken.ThrowIfCancellationRequested();
The try catch block doesn't handle the exception.
public EnumerableObservable(IEnumerable<T> enumerable)
{
this.enumerable = enumerable;
this.cancellationSource = new CancellationTokenSource();
this.cancellationToken = cancellationSource.Token;
this.workerTask = Task.Factory.StartNew(() =>
{
try
{
foreach (var value in this.enumerable)
{
//if task cancellation triggers, raise the proper exception
//to stop task execution
cancellationToken.ThrowIfCancellationRequested();
foreach (var observer in observerList)
{
observer.OnNext(value);
}
}
}
catch (AggregateException e)
{
Console.Write(e.ToString());
}
}, this.cancellationToken);
}
AggregateExceptions are thrown when a possible multitude of exceptions during asynchronous operations occured. They contain all exceptions that were raised e.g. in chained Tasks (via .ContinueWith) or within cascaded async/await calls.
As #Mitch Stewart pointed out, the correct exception-type to handle would be OperationCancelledException in your example.
Since ThrowIfCancellationRequested() throws an exception of type OperationCanceledException, you must catch OperationCanceledException or one of its base classes.
https://msdn.microsoft.com/en-us/library/system.operationcanceledexception(v=vs.110).aspx

Getting the completed task Id using Task.WaitAny()

I know that my goal can be achieved by using Task.WhenAny() but I will not deal with async await if I can avoid it, in order to prevent deadlocks. I have following code:
try
{
Task.WaitAny(this.Tasks.ToArray());
foreach (var task in this.Tasks)
{
if (task.IsFaulted || task.IsCanceled)
{
if (task.Exception.InnerException is OperationCanceledException)
{
}
}
}
}
catch (OperationCanceledException o)
{
// Handling cancelled tasks
}
catch (Exception e)
{
// Handling faulted tasks
}
And I for instance want to know exactly the id of my task, which has faulted or the Id of my task which has been cancelled. I have tried to do so as shown in the try block above, but this is not a solution since it also will throw an exception for tasks that has been cancelled before. Can I obtain a solution for this problem using Task.WaitAny() ?.
From the documentation of Task.WaitAny:
Return Value
Type: System.Int32
The index of the completed Task object in the tasks array.
So you can do this:
var taskIndex = Task.WaitAny(this.Tasks.ToArray());
var task = this.Tasks[taskIndex];

Exception is not caught at Cancelation of Task.Run

I have a class Worker which is doing some work (with simulated workload):
public class Worker
{ ...
public void DoWork(CancellationToken ct)
{
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested();
Thread.Sleep(2000);
}
}
Now I want to use this method in a Task.Run (from my Windows Forms App,at button-click) which can be cancelled:
private CancellationTokenSource _ctSource;
try
{
Task.Run(() =>
{
_worker.DoWork(_ctSource.Token);
},_ctSource.Token);
}
catch (AggregateException aex)
{
String g = aex.Message;
}
catch (OperationCanceledException ex)
{
String g = ex.Message;
}
catch (Exception ex)
{
String g = ex.Message;
}
But when the task is started, I can't cancel it with _ctSource.Cancel();
I get an error in visual studio that the OperationCanceledException is not handled!
But I surrounded the Task.Run Call in a try-catch-clause! The Exception which ocurrs in the Worker object should thrown up or not?
What is the problem?
Your Task.Run call creates the task and then returns immediately. It doesn't ever throw. But the task it creates may fail or be canceled later on.
You have several solutions here:
Use await:
await Task.Run(...)
Attach a continuation depending on the failure/cancellation case:
var task = Task.Run(...);
task.ContinueWith(t => ..., TaskContinuationOptions.OnlyOnCanceled);
task.ContinueWith(t => ..., TaskContinuationOptions.OnlyOnFaulted);
Attach a single continuation on failure:
Task.Run(...).ContinueWith(t => ..., TaskContinuationOptions.NotOnRanToCompletion);
The solution you can/should use depends on the surrounding code.
You need to new the token
private CancellationTokenSource _ctSource = new CancellationTokenSource();
Why are throwing an expectation in DoWork?
Exception from one thread don't bubble up another thread that started the thread.
Cancellation in Managed Threads
If a parallel Task throws an exception it'll return execution and will have it's Exception property (as an AggregateException, you should check for its InnerException) set (and either its IsCanceled or IsFaulted property set to true). Some minimal sample code from a project of mine which escalates the exception to the main thread:
var t = new Task(Initialize);
t.Start();
while (!t.IsCompleted && !t.IsFaulted)
{
// Do other work in the main thread
}
if (t.IsFaulted)
{
if (t.Exception != null)
{
if(t.Exception.InnerException != null)
throw t.Exception.InnerException;
}
throw new InvalidAsynchronousStateException("Initialization failed for an unknown reason");
}
If you use a CancellationTokenSource it should be easy to enhance this to check for IsCanceled (instead of IsFaulted)
You can also use Task.Wait() instead of the while loop... in my project and in that precise case it seemed more appropiate to use the while loop, but you need to wait for the Task to end in one way or another.
If you use Task.Run() you can use a .ContinueWith(Task) which will have the original task passed in (where you can check for IsFaulted or IsCanceled), or have it run only on faulted execution, at your will.

Categories

Resources