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);
}
Related
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.
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.
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 };
}
});
TaskScheduler ts= TaskScheduler.FromCurrentSynchronizationContext();
try
{
Task<T> task1 = ...
task1.ContinueWith(t =>
{
...
Task<T> task2 = ...
task2.ContinueWith(u =>
{
...
Task<T> task3 = ...
task3.ContinueWith(w =>
{
...
}, new CancellationToken(), TaskContinuationOptions.OnlyOnRanToCompletion, ts);
}, new CancellationToken(), TaskContinuationOptions.OnlyOnRanToCompletion, ts);
}, new CancellationToken(), TaskContinuationOptions.OnlyOnRanToCompletion, ts);
}
catch(Exception)
{
MessageBox.Show("...");
}
Hi. I have some code (as above). This doesn't work for me. I have three tasks which are working server-side but modifying UI, so all of them should result make in UI thread. What is more: 3rd task cannot run if 2nd complete with failure, and 2nd cannot run unless 1st is successfully completed. So if 1st one ends with a failure my tasks tree should throw exception and break rest of operations. How to achieve that in the simplest way?
UPDATE
now my code looks like
private async void SomeMethod()
{
...
try
{
var r1 = await Method1(...);
var r2 = await Method2(...);
var r3 = await Method3(...);
}
catch
{
MessageBox.Show("...");
}
}
private Task<...> Method1(...)
{
Task<...> task = Task<...>.Factory.StartNew(() =>
{
...
try
{
// Result is null (but that's ok) so 'index out of range exception' is thrown
// It calls my method MyException with this exception (but I don't know in
// which thread and how to catch this (shouldn't be catch by async SomeMethod?)
result = ....Results[0];
}
catch (Exception ex)
{
MyException(ex);
}
return result;
});
return task;
}
public void MyException(Exception ex)
{
throw ex;
}
But I still cannot catch exception.
EDIT
Solved. I don't catch exceptions (just ignore in Method1) and:
var r1 = await Method1(...);
if(r1!=null)
{
var r2 = await Method2(...);
var r3 = await Method3(...);
}
else
{
...do sth instead of catching ex
}
The easiest option is to use await here, as it will provide the error handling semantics you want with very little effort; allowing you to write code as if it were regular synchronous code:
try
{
var firstResult = await SomethingAsync();
var secondResult = await SomethingElseAsync(firstResult);
var finalResult = await AnotherThingAsync(secondResult);
}
catch
{
//handle an exception thrown by any of the above async operations.
}
If you can't do that (due to being on .NET 4.0), then you can use the Then method described here to also get the semantics you want:
var finalTask = SomethingAsync()
.Then(firstResult => SomethingElseAsync(firstResult))
.Then(secondResult => AnotherThingAsync(secondResult));
finalTask.ContinueWith(t => HandleError(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
Then is essentially a call to ContinueWith, but with different error handling semantics. If the tasks being continued threw an exception then Then just propagates that exception, rather than running the continuation. ContinueWith just runs the continuation and swallows the exception.
task1:
public Task SendMail(string email)
{
...
}
task2:
public Task<int> SaveToDB(User user)
{
...
}
What I need to do:
when task1 fail, should return a "send failure" infomation;
when task1 success, task2 begin.
when task2 fail, should return a "save failure" infomation.
when task2 success, should return a "save success" infomation.
Please help me find a solution:
public Task<string> SendAndSave(User user){
var task1 = SendMail(user.Email);
var task1Failed = task1.ContinueWith(t =>
{
var e = task1.Exception;
return "Send Failed";
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
var task2 = task1.ContinueWith(t =>
{
var save = SaveToDB(user);
try
{
int result = save.Result;
return "Save Succeeded";
}
catch(AggregateException ae)
{
return "Save Failed";
}
},
TaskContinuationOptions.NotOnFaulted);
return Task.Factory.ContinueWhenAny(new[] {task1Failed, task2}, t => t.Result);
}
When I run it I receive an error.
I invoked it as:
var result = SendAndSave(user).Result;
the error occured at:
public Task<string> SendAndSave(User user)
{
...
return Task.Factory.ContinueWhenAny(new[] {task1Failed, task2}, t => t.Result); //Here: A task was cancelled
}
After debugging it , I have two questions:
Q1: after task1, task1Failed, task2 are created, the value of each
CreationOption property is "none" although its Status is
"WaitingForActivation". It seems all the continuation options are
invalid.
Q2: For the task of Task.Factory.ContinueWhenAny(new[] {task1Failed,
task2}, t => t.Result), let's name it "factoryTask", the
ContinueWhenAny seems invalid. After put a breakpoint in the internal
of each of task1Failed, task2 and factoryTask, I can see occasionally
the breakpoint in the factoryTask was hitted firstly, though it
should be hitted after either task1Failed or task2 had completed.
Anybody can help ? Thanks.
Although I don't fully understand your questions, I understand the issue here, basically you will always have either task1Failed or task2 completes but not both, and the other one will be cancelled!
So in WaitOnAny call, if it finds one of the tasks is already cancelled, it will crash, and this really depends on the order of the tasks parameter, so in your example above you pass task1failed first, so if this task completeted ContinueWhenAny will work fine because the first task it checks is already completed so it doesn't check the other one, but if task1Failed didn't run so it is in a cancelled state, ContinueWhenAny will throw!
To avoid this, you need to use TaskCompletetionSource instead, and the task which runs, will set the value, then at the end you return tcs.Task.Result
public Task<string> SendAndSave(User user){
var tcs = new TaskCompletionSource<string>();
var task1 = SendMail(user.Email);
var task1Failed = task1.ContinueWith(t =>
{
var e = task1.Exception;
tcs.TrySetResult("Send Failed");
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
var task2 = task1.ContinueWith(t =>
{
var save = SaveToDB(user);
try
{
int result = save.Result;
tcs.TrySetResult("Save Succeeded");
}
catch(AggregateException ae)
{
tcs.TrySetResult("Save Failed");
}
},TaskContinuationOptions.NotOnFaulted);
return tcs.Task.Result;
}