How to create pass through Task.ContinueWith? - c#

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.

Related

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

Task not in a faulted state when an exception is thrown

I can't seem to figure out why my task is not in a faulted state when an exception is thrown in the following sample (assume engine.Send(...) throws an exception):
var receiverTask = new Task<Task>(async () => {
result = await Task.FromResult(engine.Send(someObject));
});
receiverTask.Start();
receiverTask.Wait();
if I do receiverTask.IsFaulted after the receiverTask.Wait() it returns false instead of true as I would expect.
I have been reading that I need to use something like the following construct:
receiverTask.ContinueWith(t => { /* error handling */ },
TaskContinuationOptions.OnlyOnFaulted);
But I can't seem to figure out how to integrate this into the sample code. Do I call this before the Start()? before the Wait()?
I would like the calling program to somehow be aware that the receiverTask has thrown an exception and failed. Any pointers?
If I correctly understand this context, you could leave out the .Start() and use .Wait() somewhat differently.
(I don't know what your engine.Send(someObject) method is, so I have to assume.)
Let's say your method returns a bool (it doesn't matter, but just to write something down.
If it is synchronous, the returned Task could be:
Task<bool> receiverTask = Task<bool>.Run(async () =>
await Task<bool>.FromResult(engine.Send(someObject)));
or
Task<bool> receiverTask = Task.Factory.StartNew(async delegate
{
bool _result = await Task<bool>.FromResult(engine.Send(someObject));
return _result;
}, TaskScheduler.Default).Unwrap();
If it is asynchronous, the returned Task could be:
Task<bool> receiverTask = Task.Run(async () =>
await engine.Send(someObject));
or
Task<bool> receiverTask = Task.Factory.StartNew(async delegate
{
bool _result = await engine.Send(someObject);
return _result;
}, TaskScheduler.Current).Unwrap<bool>();
(However, all these are blocking if there's no other async/await declaration).
Once your Task is scheduled, some Continuation condition can be defined to evaluate its status.
If the Task is in some faulty status (canceled, aborted etc.) its .IsCompleted property reports true, but the .Status property is not TaskStatus.RanToCompletion.
receiverTask.ContinueWith(t =>
{
//Continue on faulted
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
if (receiverTask.IsFaulted)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnFaulted).Wait(0);
receiverTask.ContinueWith(t =>
{
//Continue on canceled
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
if (receiverTask.IsCanceled)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnCanceled).Wait(0);
receiverTask.ContinueWith(t =>
{
//Standard behaviour
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
Console.WriteLine(receiverTask.Status.ToString());
}, TaskContinuationOptions.None).Wait();
//This writes only if no errors have been raised
if (receiverTask.Status == TaskStatus.RanToCompletion)
Console.WriteLine("Completed: {0} Result: {1}", receiverTask.GetAwaiter().IsCompleted, receiverTask.Result);
But you might also use a Try/Catch block:
try
{
receiverTask.Wait();
if (receiverTask.Status == TaskStatus.RanToCompletion)
Console.WriteLine("Completed: {0} Result: {1}", receiverTask.GetAwaiter().IsCompleted, receiverTask.Result);
}
catch (Exception)
{
receiverTask.ContinueWith(t =>
{
//With continuation
if (receiverTask.IsFaulted)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnFaulted);
//or without
//if (receiverTask.IsCanceled)
//Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}

Catch and propagate exceptions in async methods

I have the following code in a while loop:
Task worker =
Task.Factory
.StartNew(() =>
{
ProduceAsync(param, tokenSource.Token);
}, tokenSource.Token)
.ContinueWith((t) =>
{
OnException(t);
}, TaskContinuationOptions.OnlyOnFaulted);
ProduceAsync looks like this:
private async Task ProduceAsync( List<string> param, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var task = _requestManager.GetProductsAsync(param);
var products;
try
{
products = await task;
_products.Add(products);
}
catch
{
task.GetAwaiter().GetResult();
}
}
When there's an exception inside the async request I would like the program to fail fast, so on OnException(t) I'm cancelling the tasks using tokenSource. The problem here that ProduceAsync does not propagate the exception and OnException isn't being called. How can I get it to propagate the exception?
Note: I copied the code by hand to my phone so there might be compilation errors...
I'm reasonably sure that instead of
Task.Factory.StartNew(() => {
ProduceAsync(param, tokenSource.Token);
}, tokenSource.Token)
.ContinueWith(...
you should be doing:
Task.Run(async () => {
await ProduceAsync(param, tokenSource.Token);
}, tokenSource.Token)
.ContinueWith(...
basically if you don't wait for your async method to complete then it will run separately and the delegate you pass to Task.Factory.StartNew will return immediately. Also you should use Task.Run as its a bit safer.
Also, you may want to do a rethrow with throw in your catch block after you do whatever you want with the exception.

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 };
}
});

Awaited task does not handle exception gracefully

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.

Categories