Awaited task does not handle exception gracefully - c#

I expected that I could use this pattern:
var task = Task.Run(() => Agent.GetUserType(Instance));
await task;
string information = task.GetExceptionOrStatus(); // extension method
When my sql server is not started - just as a testcase - I see that the exception that the WCF service throws, is not handled gracefully by the task, I get:
An exception of type 'System.ServiceModel.FaultException' occurred in Microsoft.Threading.Tasks.dll but was not handled in user code.
I was under the impression that my code would be able to extract the error from the task object.
How should I do it better?

await will propagate any exceptions on that task, by design.
var task = Task.Run(() => Agent.GetUserType(Instance));
try
{
await task;
}
catch (Exception ex)
{
// TODO
}
string information = task.GetExceptionOrStatus(); // extension method

If you don't want await to throw, you could create a new Task based on the previous Task, that never fails. Something like:
static async Task<T> IgnoreException<T>(this Task<T> task)
{
try
{
return await task;
}
catch
{
return default(T);
}
}
Alternative implementation:
static Task<T> IgnoreException<T>(this Task<T> task)
{
return task.ContinueWith(t => t.Exception == null ? t.Result : default(T));
}
Usage then would look like this:
await task.IgnoreException();
string information = task.GetExceptionOrStatus(); // extension method

You should surround your await task; in a try/catch block. If an Exception occurs, it will throw an AggregateException containing all the exceptions that occurred within your Task.

Related

Converting sync to async code got unhandled System.OperationCanceledException: 'The operation was canceled.'

I'm trying to convert my sync functions to async. In all my sync functions I have a cancellation token which is used on function, task and parallel blocks. I have a try/catch block before calling the async function, but I'm getting an unhandled exception:
Exception thrown: 'System.OperationCanceledException' in
System.Threading.Tasks.Parallel.dll An exception of type
'System.OperationCanceledException' occurred in
System.Threading.Tasks.Parallel.dll but was not handled in user code
The operation was canceled.
My async function:
public async Task DecodeAsync(string? fileFullPath, FileDecodeType fileDecodeType, OperationProgress? progress = null) =>
await Task.Run(() => Decode(fileFullPath, fileDecodeType, progress), progress?.Token ?? default);
How I call it:
try
{
await SlicerFile.DecodeAsync(fileName, fileDecodeType, Progress);
}
catch (OperationCanceledException) { } // Do not work!
catch (Exception exception) // Works for other exceptions
{
await this.MessageBoxError(exception.ToString(), "Error opening the file");
}
catch (OperationCanceledException) is never reached nor catch (Exception exception) in a cancel event. As my try is at top most, why doesn't it catch the exception?
But if I do:
public async Task DecodeAsync(string? fileFullPath, FileDecodeType fileDecodeType, OperationProgress? progress = null) =>
await Task.Run(() => throw new Exception("Test"));
I get the exception catch on the generic Exception (it's handled)
In other hand with old code it's working and handling the OperationCanceledException:
var task = await Task.Factory.StartNew( () =>
{
try
{
SlicerFile.Decode(fileName, fileDecodeType, Progress);
return true;
}
catch (OperationCanceledException) {} // Works!
catch (Exception exception)
{
Dispatcher.UIThread.InvokeAsync(async () =>
await this.MessageBoxError(exception.ToString(), "Error opening the file"));
}
return false;
});
What am I doing wrong?
The results of Task.Run don't need to be awaited right there necessarily. You can just return the running Task and then the method doesn't need to be awaited or be async any longer.
Task DecodeAsync(string? fileFullPath, FileDecodeType fileDecodeType, OperationProgress? progress = null) => Task.Run(() =>
Decode(fileFullPath, fileDecodeType, progress), progress?.Token ?? default);
And since you're passing in progress with a token, you can monitor that to exit the decode method cleanly instead of trying to catch and ignore the operation cancelled exception.
You'll have better luck if you make the decode method itself asynchronous if you can. It already returns a Task, so it could return a Task (or whatever). Your old code is also asynchronous in the same way, so there's no advantage to your new code that I can see.

Cannot handle exceptions thrown in a task and continue where it left off

I have defined an extension method to capture exceptions thrown in a task and write it to log.
public static Task IgnoreExceptions(this Task task)
{
task.ContinueWith(t =>
{
var ignored = t.Exception;
Console.WriteLine("Error" + ignored.Message);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted);
return task;
}
However, it doesn't seem to work for me. The execution does not continue where it should be. Can anyone tell what's wrong with my IgnoreExceptions?
try {
await DoSomething().IgnoreExceptions().ConfigureAwait(false);
await DoSomethingElse().IgnoreExceptions().ConfigureAwait(false);
}
catch(Exception e){
// If DoSomething() throws, the error is written to console but the code reaches here instead of continuing to call DoSomethingElse()
}
The problem of your implementation is that you're returning the original task. Return the one that is created by ContinueWith might work (I haven't tested it).
old:
task.ContinueWith(...);
return task;
new:
var result = task.ContinueWith(...);
return result;
But I would prefer an async/await approach and rewrite the extension method like that:
public static async Task IgnoreException(this Task task)
{
try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
DEMO

How to create pass through Task.ContinueWith?

I want to tack on a task at the end of an original task, but would like to preserve the original result and type. The appended task is just for logging purpose, such as writing to console, etc. For example:
Task.Run(() => DateTime.Now.Hour > 12 ? "Hey!" : throw new Exception())
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
{
Console.WriteLine("Success");
return t.Result;
}
else
{
Console.WriteLine("Failure");
throw t.Exception;
}
});
The type of the original task is Task<string>. Here I return t.Result if the task doesn't encounter error, and I throw t.Exception in case the task encounters error. Looks like the type remains Task<string> but not sure about the exception side.
Is this the right way to do it? Or is there a better way?
There is no reason to rethrow the exception. The task will throw AggregrateException and you can get the real exceptions with InnerExceptions property to handle them.
For logging, you can separate success and failure using TaskContinuationOptions:
var t = Task.Run(() => DateTime.Now.Hour > 12 ? "Hey!" : throw new Exception());
t.ContinueWith(_ => Console.WriteLine("Success"), TaskContinuationOptions.OnlyOnRanToCompletion);
t.ContinueWith(_ => Console.WriteLine("Faiure"), TaskContinuationOptions.OnlyOnFaulted);
The success will be logged only if task executed successfully until the end. And failure will be logged if there was an unhandled exception.
This separates logging and getting the result. So you can just get the result from the first task.
My preferred solution to this problem is to return the t itself, resulting in a continuation of type Task<Task<string>>, and then Unwrap it to get a Task<string>:
Task<string> task = Task.Run(() =>
{
return DateTime.Now.Hour > 12 ? "Hey!" : throw new Exception();
});
Task<string> continuation = task.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
{
Console.WriteLine($"Success: {t.Result}");
}
else
{
Console.WriteLine("Failure or Cancellation");
}
return t;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default)
.Unwrap();
This approach guarantees that all the errors of the original task will be propagated through the continuation, or, if the task is canceled, that the continuation will be also canceled with the same CancellationToken stored inside it.
In the vast majority of cases the tasks fail with only one error¹, so it's simpler to use async/await instead of the cumbersome ContinueWith method:
async Task<T> OnCompletionLog<T>(Task<T> task)
{
try
{
T result = await task.ConfigureAwait(false);
Console.WriteLine($"Success: {t.Result}");
return result;
}
catch
{
Console.WriteLine("Failure or Cancellation");
throw;
}
}
Task<string> continuation = OnCompletionLog(task);
¹ AFAIK the tasks that can fail with more than one error are the Task.WhenAll, the Parallel.ForEachAsync, and the IDataflowBlock.Completion.

Exception from PLinq AsParallel async crashes the app

My question is about capturing exceptions in ForAll method under Plinq
I was trying to run tasks concurently with setting max number of threads.
Using
enumerable
.AsParallel()
.WithDegreeOfParallelism(100)
.ForAll(async item => await AsyncTask())
It works, but if AsyncTask throws exception the app crashes.
I have done the following test:
try
{
IEnumerable<string> enumerable = new List<string> { "st", "st" };
enumerable.AsParallel()
.ForAll(async f =>
{
try
{
throw new Exception(); // Or await AsyncTask that throws this
}
catch (Exception e)
{
e.ToString(); **// This Exception is captured**
throw e;
}
});
}
catch (Exception e) **// THIS IS NOT CAPTURED AND THE APP CRASHES**
{
e.ToString();
}
And I would like to understand the reasons for this
And other options to implement
enumerable.AsParallel().ForAll() executes the given action for each element of your enumeration in parallel. Since your given action is async by itself, ForAll() does not wait until all actions completed. In this case the executed code leaves the try..catch block before your AsyncTask() method throws the exception. This may lead to an unhandled exception, which crashes your app.
It does not matter, that you try to await the AsyncTask(), because ForAll() gets a plain Action and does not await the result of your AsyncTask().
A possible solution could be to start your AsyncTasks for each element without AsParallel().ForEach() and await the results later inside your try..catch.
When storing the
Task or Task<T>
result in a list you can check if any task was throwing an exception using the task.Exception property.
You can do something like this:
private async Task DoSomethingAsync()
{
try
{
IEnumerable<string> enumerable = new List<string> { "st", "st" };
// start all tasks and store them in an array
var tasks = enumerable.Select(TaskAsync).ToArray();
// do something more without waiting until all tasks above completed
// ...
// await all tasks
var completionTask = Task.WhenAll(tasks);
await completionTask;
// handle task exception if any exists
if (completionTask.Status == TaskStatus.Faulted)
{
foreach (var task in tasks)
{
if (task.Exception != null)
{
// throw an exception or handle the exception, e.g. log the exceptions to file / database
}
}
}
}
catch (Exception e)
{
// handle your exception, e.g. write a log to file / database
}
}
private Task TaskAsync(string item)
{
// Task.Delay() is just a placeholder
// do some async stuff here, e.g. access web services or a database
return Task.Delay(10000);
}

Is there a way to Wait for a TPL Task without in throwing an exception?

Some of us prefer to code in an exception-light style. However, if you wait for a Task Parallel Library task, and the task threw an exception, it will throw an exception on the calling thread as well. Is there a (preferably standard) way to avoid this behaviour and just check the response for exceptions when you get it back?
You can use Task.WaitAny like:
var task = Task.Run(() =>
{
// ...
throw new Exception("Blah");
});
Task.WaitAny(task);
if (task.IsFaulted)
{
var error = task.Exception;
// ...
}
else if (task.IsCanceled)
{
// ...
}
else
{
// Success
}
You can't wait on a faulted task without raising an exception.
But you can wait on a continuation to that task, which will complete only after the original task completed without raising an exception:
public static Task SwallowExceptions(this Task task)
{
return task.ContinueWith(_ => { });
}
faultedTask.SwallowExceptions().Wait();
if (faultedTask.IsFaulted)
{
// handle exception
}
If your task returns a value, you can represent that in the extensions method and return the actual value if there were no exceptions or the default value if there were:
public static Task<T> SwallowExceptions<T>(this Task<T> task)
{
return task.ContinueWith(completedTask =>
completedTask.IsFaulted
? default(T)
: completedTask.Result);
}
Unfortunately, this functionality is not built-in. Use this workaround:
myTask.ContinueWith(_ => { }, TaskContinuationOptions.ExecuteSynchronously).Wait();
You can make this into an extension method.
Based on what you have written, might catching the exception and checking the IsFaulted property be your solution? IsFaulted
Catch the exception within the Task and return it in the result?
var task1 = Task.Factory.StartNew(() =>
{
try
{
throw new MyCustomException("I'm bad, but not too bad!");
}
catch(Exception ex)
{
return new Result { Error = ex };
}
});

Categories