TaskContinuationOptions OnlyOnCancelled catches unhandled errors - c#

I have following code to handle my TaskContinuations. I am bit confused because I have below OnlyOnFaulted block which I expect will be entered if the task throws an unhandled exception.
However, unhandled exception, handled exception that is rethrown using throw, or cancellation will land in the OnlyOnCanceled block.
GetDataAsync(id).ContinueWith((antecedant) =>
{
// do something when async method completed
}, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((antecedant) =>
{
var error = antecedant.Exception.Flatten(); //so when is this called if everything is cought by OnCancelled below?
}, TaskContinuationOptions.OnlyOnFaulted)
.ContinueWith((antecedant) =>
{
// this is fired if method throws an exception or if CancellationToken cancelled it or if unhandled exception cought
var error = "Task has been cancelled";
}, TaskContinuationOptions.OnlyOnCanceled);
I would expect that re-thrown errors and cancellations will land in the OnlyOnCanceled block whereas unhandled exception will land in the OnlyOnFaulted block
Note that I cannot just await GetDataAsync because this is called in a method called from View's c-tor. I explained that in this post NetworkStream ReadAsync and WriteAsync hang infinitelly when using CancellationTokenSource - Deadlock Caused by Task.Result (or Task.Wait)
UPDATE
Instead using code above, I am using Task.Run like below. I am decorating the lambda passed into Task.Run with async to provide "Async all the way" as recommended by Jon Goldberger at https://blog.xamarin.com/getting-started-with-async-await/
Task.Run(async() =>
{
try
{
IList<MyModel> models = await GetDataAsync(id);
foreach (var model in models)
{
MyModelsObservableCollection.Add(model);
}
} catch (OperationCancelledException oce) {}
} catch (Exception ex) {}
});
This felt a better solution since I can wrap the code inside Task.Run with try...catch block and the exception handling is behaving as I would expect.
I am definitely planning to give a try to suggestion offered by Stephen Cleary at https://msdn.microsoft.com/en-us/magazine/dn605875.aspx as it seam to be a cleaner solution.

As I said in my other answer, you should use await, and, since this is a constructor for a ViewModel, you should synchronously initialize to a "Loading..." state and asynchronously update that ViewModel to a "Display Data" state.
To answer this question directly, the problem is that ContinueWith returns a task representing the continuation, not the antecedent. To simplify the code in your question:
GetDataAsync(id)
.ContinueWith(A(), TaskContinuationOptions.OnlyOnRanToCompletion);
.ContinueWith(B(), TaskContinuationOptions.OnlyOnFaulted)
.ContinueWith(C(), TaskContinuationOptions.OnlyOnCanceled);
A() will be called if GetDataAsync(id) runs to completion. B() will be called if A() faults (note: not if GetDataAsync(id) faults). C() will be called if B() is canceled (note: not if GetDataAsync(id) is canceled).
There are a couple of other problems with your usage of ContinueWith: it's missing some flags (e.g., DenyChildAttach), and it's using the current TaskScheduler, which can cause surprising behavior. ContinueWith is an advanced, low-level method; use await instead.

Related

A canceled task propagates two different types of exceptions, depending on how it is waited. Why?

I encountered a strange behavior while writing some complex async/await code. I managed to create accidentally a canceled Task with a dual (schizophrenic) identity. It can either throw a TaskCanceledException or an OperationCanceledException, depending on how I wait it.
Waiting it with Wait throws an AggregateException, that contains a TaskCanceledException.
Waiting it with await throws an OperationCanceledException.
Here is a minimal example that reproduces this behavior:
var canceledToken = new CancellationToken(true);
Task task = Task.Run(() =>
{
throw new OperationCanceledException(canceledToken);
});
try { task.Wait(); } // First let's Wait synchronously the task
catch (AggregateException aex)
{
var ex = aex.InnerException;
Console.WriteLine($"task.Wait() failed, {ex.GetType().Name}: {ex.Message}");
}
try { await task; } // Now let's await the same task asynchronously
catch (Exception ex)
{
Console.WriteLine($"await task failed, {ex.GetType().Name}: {ex.Message}");
}
Console.WriteLine($"task.Status: {task.Status}");
Output:
task.Wait() failed, TaskCanceledException: A task was canceled.
await task failed, OperationCanceledException: The operation was canceled.
task.Status: Canceled
Try it on Fiddle.
Can anyone explain why is this happening?
P.S. I know that the TaskCanceledException derives from the OperationCanceledException. Still I don't like the idea of exposing an async API that demonstrates such a weird behavior.
Variants: The task below has a different behavior:
Task task = Task.Run(() =>
{
canceledToken.ThrowIfCancellationRequested();
});
This one completes in a Faulted state (instead of Canceled), and propagates an OperationCanceledException with either Wait or await.
This is quite puzzling because the CancellationToken.ThrowIfCancellationRequested method does nothing more than throwing an OperationCanceledException, according to the source code!
Also the task below demonstrates yet another different behavior:
Task task = Task.Run(() =>
{
return Task.FromCanceled(canceledToken);
});
This task completes as Canceled, and propagates a TaskCanceledException with either Wait or await.
I have no idea what's going on here!
Summarising multiple comments, but:
Task.Wait() is a legacy API that pre-dates await
for historic reasons, .Wait() would manifest cancellation as TaskCanceledException; to preserve backwards compatibility, .Wait() intervenes here, to expose all OperationCanceledException faults as TaskCanceledException, so that existing code continues to work correctly (in particular, so that existing catch (TaskCanceledException) handlers continue to work)
await uses a different API; .GetAwaiter().GetResult(), which behaves more like you would expect and want (although it is not expected to be used until the task is known to have completed), BUT!
in reality, you should almost never use .Wait() or .GetAwaiter().GetResult(), preferring await in almost all cases - see "sync over async"
if you're consuming an async API, and you choose (for whatever reason) to use .Wait() or GetAwaiter().GetResult(), then you are stepping into danger, and any consequences are now entirely your fault as the consumer; this is not something that a library author can, or should, compensate for (other than providing twin synchronous and asynchronous APIs)
in particular, note that while you might get away with subverting the awaiter API with Task[<T>], this pattern with various other awaitables would be an undefined behaviour (to be honest, I'm not sure it is really "defined" for Task[<T>])
equally: any deadlocks caused by "sync over async" (usually sync-context related) are entirely the problem of the consumer invoking a synchronous wait on an awaitable result
if in doubt: await (but equally, only await once; using await multiple times is also an undefined behaviour for awaitables other than Task[<T>])
for your exception handling: prefer catch (OperationCanceledException) over catch (TaskCanceledException), since the former will handle both via inheritance

Will awaiting multiple tasks observe more than the first exception?

Today my colleagues and I discussed how to handle exceptions in C# 5.0 async methods correctly, and we wondered if awaiting multiple tasks at once also observes the exceptions that do not get unwrapped by the runtime.
Consider the following code snippet:
async Task ExceptionMethodAsync()
{
await Task.Yield();
throw new Exception();
}
async Task CallingMethod()
{
try
{
var a = ExceptionMethodAsync();
var b = ExceptionMethodAsync();
await Task.WhenAll(a, b);
}
catch(Exception ex)
{
// Catches the "first" exception thrown (whatever "first" means)
}
}
What happens to the second task now? Both will be in a faulted state, but is the second task's exception now observed or unobserved?
Task.WhenAll returns a task and like all tasks the Exception property holds an AggregateException that combines all exceptions.
When you await such a task only the first exception will actually be thrown.
... Whether because of child tasks that fault, or because of combinators like Task.WhenAlll, a single task may represent multiple operations, and more than one of those may fault. In such a case, and with the goal of not losing exception information (which can be important for post-mortem debugging), we want to be able to represent multiple exceptions, and thus for the wrapper type we chose AggregateException.
... Given that, and again having the choice of always throwing the first or always throwing an aggregate, for “await” we opt to always throw the first
from Task Exception Handling in .NET 4.5
It's up to you to choose if you want to handle just the first using await task; (true in most cases) or handle all using task.Exception (as in my example below), but in both cases a and b would not raise an UnobservedTaskException.
var task = Task.WhenAll(a, b);
try
{
await task;
}
catch
{
Trace.WriteLine(string.Join(", ", task.Exception.Flatten().InnerExceptions.Select(e => e.Message)));
}

Async method throws exception instantly but is swallowed when async keyword is removed

I'm getting some behaviour which I cannot understand when throwing exceptions in async methods.
The following code will throw an exception immediately when calling the ThrowNow method. If I comment that line out and throw the exception directly then the exception is swallowed and not raised in the Unobserved event handler.
public static async void ThrowNow(Exception ex){
throw ex;
}
public static async Task TestExAsync()
{
ThrowNow(new System.Exception("Testing")); // Throws exception immediately
//throw new System.Exception("Testing"); // Exception is swallowed, not raised in unobserved event
await Task.Delay(1000);
}
void Main()
{
var task = TestExAsync();
}
Something a little more confusing, if I remove the async keyword from the ThrowNow method, the exception is swallowed yet again.
I thought async methods run synchronously until reaching a blocking method. In this case, it seems that removing the async keyword is making it behave asynchronously.
I thought async methods run synchronously until reaching a blocking method.
They do, but they're still aware that they're executing within an asynchronous method.
If you throw an exception directly from an async void method, the async mechanism is aware that you'd have no way of observing that exception - it won't be thrown back to the caller, because exceptions thrown in async methods are only propagated through tasks. (The returned task becomes faulted.) It would be odd for an exception thrown before the first blocking await expression to be thrown directly, but exceptions afterwards to be handled differently.
As far as I'm aware, an exception thrown by an async void method is passed directly to the synchronization context, if there is one. (A continuation is posted to the synchronization context that just throws the exception.) In a simple console app, there isn't a synchronization context, so instead it's thrown as an unreported exception.
If you change your void method to return Task, then instead you'll just have an exception which could have been observed, but isn't (because you're not using the return value in TestExAsync).
Does that make any sense? Let me know if you'd like more clarification - it's all a bit tortuous (and I don't know how well documented it is).
EDIT: I've found a bit of documentation, in the C# 5 spec section 10.15.2:
If the return type of the async function is void, evaluation differs from the above in the following way: Because no task is returned, the function instead communicates completion and exceptions to the current thread's synchronization context. The exact definition of synchronization context is implementation-dependent, but is a representation of "where" the current thread is running. The synchronization context is notified when evaluation of a void-returning async function commences, completes successfully, or causes an uncaught exception to be thrown.
Something a little more confusing, if I remove the async keyword from the ThrowNow method, the exception is swallowed yet again.
Exceptions are not "swallowed".
In addition to what Jon Skeet said, consider this code where ThrowNow is not marked async:
static void ThrowNow(Exception ex)
{
throw ex;
}
static async Task TestExAsync()
{
ThrowNow(new System.Exception("Testing"));
await Task.Delay(1000);
}
static void Main()
{
var task = TestExAsync();
Console.WriteLine(task.Exception);
}
As you can see, exceptions are not "swallowed", they're just communicated to you in the task returned from an asynchronous method.
Obviously, that also means you cannot try catch them, unless you await the task:
static void Main()
{
AsyncMain();
}
static async void AsyncMain()
{
var task = TestExAsync();
try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}

Why doesn't this exception get thrown?

I use a set of tasks at times, and in order to make sure they are all awaited I use this approach:
public async Task ReleaseAsync(params Task[] TaskArray)
{
var tasks = new HashSet<Task>(TaskArray);
while (tasks.Any()) tasks.Remove(await Task.WhenAny(tasks));
}
and then call it like this:
await ReleaseAsync(task1, task2, task3);
//or
await ReleaseAsync(tasks.ToArray());
However, recently I have been noticing some strange behavior and set to see if there was a problem with the ReleaseAsync method. I managed to narrow it down to this simple demo, it runs in linqpad if you include System.Threading.Tasks. It will also work slightly modified in a console app or in an asp.net mvc controller.
async void Main()
{
Task[] TaskArray = new Task[]{run()};
var tasks = new HashSet<Task>(TaskArray);
while (tasks.Any<Task>()) tasks.Remove(await Task.WhenAny(tasks));
}
public async Task<int> run()
{
return await Task.Run(() => {
Console.WriteLine("started");
throw new Exception("broke");
Console.WriteLine("complete");
return 5;
});
}
What I don't understand is why the Exception never shows up anywhere. I would have figured that if the Tasks with the exception were awaited, it would throw. I was able to confirm this by replacing the while loop with a simple for each like this:
foreach( var task in TaskArray )
{
await task;//this will throw the exception properly
}
My question is, why doesn't the shown example throw the exception properly (it never shows up anywhere).
TL;DR: run() throws the exception, but you're awaiting WhenAny(), which doesn't throw an exception itself.
The MSDN documentation for WhenAny states:
The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.
Essentially what is happening is that the task returned by WhenAny simply swallows the faulted task. It only cares about the fact that the task is finished, not that it has successfully completed. When you await the task, it simply completes without error, because it is the internal task that has faulted, and not the one you're awaiting.
A Task not being awaited or not using its Wait() or Result() method, will swallow the exception by default. This behavior can be modified back to the way it was done in .NET 4.0 by crashing the running process once the Task was GC'd. You can set it in your app.config as follows:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
A quote from this blog post by the Parallel Programming team in Microsoft:
Those of you familiar with Tasks in .NET 4 will know that the TPL has the notion of “unobserved” exceptions. This is a compromise between two competing design goals in TPL: to support marshaling unhandled exceptions from the asynchronous operation to the code that consumes its completion/output, and to follow standard .NET exception escalation policies for exceptions not handled by the application’s code. Ever since .NET 2.0, exceptions that go unhandled on newly created threads, in ThreadPool work items, and the like all result in the default exception escalation behavior, which is for the process to crash. This is typically desirable, as exceptions indicate something has gone wrong, and crashing helps developers to immediately identify that the application has entered an unreliable state. Ideally, tasks would follow this same behavior. However, tasks are used to represent asynchronous operations with which code later joins, and if those asynchronous operations incur exceptions, those exceptions should be marshaled over to where the joining code is running and consuming the results of the asynchronous operation. That inherently means that TPL needs to backstop these exceptions and hold on to them until such time that they can be thrown again when the consuming code accesses the task. Since that prevents the default escalation policy, .NET 4 applied the notion of “unobserved” exceptions to complement the notion of “unhandled” exceptions. An “unobserved” exception is one that’s stored into the task but then never looked at in any way by the consuming code. There are many ways of observing the exception, including Wait()’ing on the Task, accessing a Task’s Result, looking at the Task’s Exception property, and so on. If code never observes a Task’s exception, then when the Task goes away, the TaskScheduler.UnobservedTaskException gets raised, giving the application one more opportunity to “observe” the exception. And if the exception still remains unobserved, the exception escalation policy is then enabled by the exception going unhandled on the finalizer thread.
From the comment:
these [tasks] were tied to managed resources and I wanted to release them when
they became available instead of waiting for all of them to complete
and then releasing.
Using a helper async void method may give you the desired behavior for both removing the finished tasks from the list and immediately throwing unobserved exceptions:
public static class TaskExt
{
public static async void Observe<TResult>(Task<TResult> task)
{
await task;
}
public static async Task<TResult> WithObservation(Task<TResult> task)
{
try
{
return await task;
}
catch (Exception ex)
{
// Handle ex
// ...
// Or, observe and re-throw
task.Observe(); // do this if you want to throw immediately
throw;
}
}
}
Then your code might look like this (untested):
async void Main()
{
Task[] TaskArray = new Task[] { run().WithObservation() };
var tasks = new HashSet<Task>(TaskArray);
while (tasks.Any<Task>()) tasks.Remove(await Task.WhenAny(tasks));
}
.Observe() will re-throw the task's exception immediately "out-of-band", using SynchronizationContext.Post if the calling thread has a synchronization context, or using ThreadPool.QueueUserWorkItem otherwise. You can handle such "out-of-band" exceptions with AppDomain.CurrentDomain.UnhandledException).
I described this in more details here:
TAP global exception handler

Exception in task not breaking immediately

Some pseudo code to illustrate my problem:
public async Task DoSomethingAsync()
{
try
{
var task1 = DoThisAsync(); // throws exception
var task2 = DoThatAsync();
await task1.Then(t => Handle(t));
await task2.Then(t => Handle(t));
}
catch (AggregateException)
{
Console.WriteLine("Whatnow?");
}
}
And Then is defined as such:
// from https://gist.github.com/rizal-almashoor/2818038
public static Task Then(this Task task, Action<Task> next)
{
var tcs = new TaskCompletionSource<AsyncVoid>();
task.ContinueWith(t=>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception); // continuing task1 this line only gets hit
// after DoThatAsync() is completed??
else
{
try
{
next(t);
tcs.TrySetResult(default(AsyncVoid));
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
});
return tcs.Task;
}
So my problem is that for some reason, even though DoThisAsync() throws an exception pretty early, I don't see "whatnow" until DoThatAsync() is finished.
This is not the exact code, I tried to simplify to not waste your time. If there's nothing here that explains this behavior let me know and I will add more detail.
Edit
For the purpose of this question we can imagine DoThisAsync() and DoThatAsync() are to asynchronouse methods that basically do the following:
DoThisAsync:
Thread.Sleep(30000); // wait a short perioud of time
throw new Exception(); // and throw an exception
DoThatAsnyc:
Thread.Sleep(240000); // wait a long period of time
Presumably your DoThisAsync starts a new task and the action of that task is what throws the exception--is that right?
In that case, the exception is stored within the Task. The exception will not be rethrown unless you call a trigger method like .Wait, or .Result. When you await the task returned from Then, it is causing that task's exception to be rethrown.
Edit:
Based on your edits showing the DoThisAsync:
When an async marked method that returns a Task causes an exception, that exception is stored in the Task (rather than allowing it to propagate). If you were to remove the async keyword I would expect the exception to happen at the time DoThisAsync is called.
Edit:
From Stephen Toub's Async/Await FAQ: http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/async-await-faq.aspx:
What does the “async” keyword do when applied to a method?
When you mark a method with the “async” keyword, you’re really telling the compiler two things:
You’re telling the compiler that you want to be able to use the “await” keyword inside the method (you can use the await keyword if and only if the method or lambda it’s in is marked as async). In doing so, you’re telling the compiler to compile the method using a state machine, such that the method will be able to suspend and then resume asynchronously at await points.
You’re telling the compiler to “lift” the result of the method or any exceptions that may occur into the return type. For a method that returns Task or Task, this means that any returned value or exception that goes unhandled within the method is stored into the result task. For a method that returns void, this means that any exceptions are propagated to the caller’s context via whatever “SynchronizationContext” was current at the time of the method’s initial invocation.

Categories

Resources