Why doesn't this exception get thrown? - c#

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

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

What happens when you await a failed task

I have a theoretical question to you. What happens if I await the Result of a Task inside another task? I want to know if my current system will work afterwards.
A task gets launched and does some stuff. At some point that task might need another one in order to process data which the current task is not able to process by itself. So I use await to ensure that the current task won't continue as long as he has not the result of the helper task. But what happens if the helper fails? Will the current task remain locked?
Can I avoid this deadlock somehow (without changing the system itself - task inside task)?
A task gets launched and does some stuff. At some point that task might need another one in order to process data which the current task is not able to process by itself. So I use await to ensure that the current task won't continue as long as he has not the result of the helper task. But what happens if the helper fails? Will the current task remain locked?
The core idea behind async and await is that asynchronous code works just about the same as synchronous code.
So, if you have synchronous code like this:
void HelperMethod()
{
throw new InvalidOperationException("test");
}
void DoStuff()
{
HelperMethod();
}
then you would expect DoStuff to propagate the InvalidOperationException from the helper method. Similarly, that's what happens with asynchronous code:
async Task HelperMethodAsync()
{
throw new InvalidOperationException("test");
}
async Task DoStuffAsync()
{
await HelperMethodAsync();
}
That is, DoStuffAsync will also propagate the InvalidOperationException.
Now, it doesn't work exactly the same way, of course, since it must be asynchronous, but the general idea is that all your control flow such as try/catch, for loops, etc, all "just work" for asynchronous code very similarly to synchronous code.
What's actually going on is that when HelperMethod ends with an InvalidOperationException, the exception is caught and placed on the returned Task, and the task is completed. When the await in DoStuffAsync sees that the task has completed, it examines its exceptions and re-raises the first one (in this case, there is only one, the InvalidOperationException). And it re-raises it in a way that preserves the call stack on the exception. This in turn causes the Task returned from DoStuffAsync to be completed with that same exception.
So, under the covers async and await are doing a bit of work to ensure that you can just call other methods with await and use try/catch the same way as you would in synchronous code. But most of the time you don't have to be aware of that.
It's really easy to test. For example:
[TestMethod, ExpectedException(typeof(Exception))]
public async Task DoFaultedTaskThrowOnAwait()
{
var task = Task.Factory.StartNew(() => { throw new Exception("Error 42"); });
await task;
}
[TestMethod, ExpectedException(typeof(AggregateException))]
public void DoFaultedTaskThrowOnWait()
{
var task = Task.Factory.StartNew(() => { throw new Exception("Error 42"); });
task.Wait();
}
Both tests pass, notice that Wait throws an AggregateException, and await throws the Exception.
What happens if I await the Result of a Task inside another task?
You can only await a Task.Result if it is an awaitable (meaning it has a GetAwaiter method). This is rarely the case. I assume you mean await on an inner Task.
But what happens if the helper fails? Will the current task remain locked?
First, im not sure what you mean by "locked". The task isn't locked while you await on the inner task. Control is yielded back to the calling method until that inner task completes. If that inner task fails and you fail to properly handle that exception, you parent task will fault as well. You need to make sure you handle exceptions gracefully:
var task =
Task.Run(async () =>
{
try
{
await AsyncHelper.DoSomethingAsync();
}
catch (Exception e)
{
// Handle exception gracefully
}
});
If you await on the parent Task, you'll notice the inner exception propogating from the unhandled inner Task.
Can I avoid this deadlock somehow?
I see no reason why your code should deadlock in this specific case. Not sure why this worries you.

Task unhandled exceptions

I'm trying to understand what is going on with exceptions that are thrown within a task object and never handled.
On MSDN it said that:
If you do not wait on a task that propagates an exception, or access
its Exception property, the exception is escalated according to the
.NET exception policy when the task is garbage-collected.
So I don't quite understand in what way those exceptions affect program flow. I thought that those exceptions should interrupt execution as soon as they are garbage-collected. But I can not design this behaviour. Within the following snippet the thrown exception doesn't show up.
// Do something ...
Task.Run (()=> {throw new Exception("Exception in the task!");});
// Do something else
Please, can anyone explain how unhandled task exception are dealt with and how they affect program flow.
You're describing the behavior as it was in .NET 4, but it will be difficult for you to force the garbage collection and actually observe that behavior. The following quote from Stephen Toub's excelent write-up on the subject should make it even more clear:
Tasks keep track of whether an unhandled exception has been
“observed.” In this context, “observed” means that code has joined
with the Task in some fashion in order to at least be made aware of
the exception. This could be calling Wait/WaitAll on the Task. It
could be checking the Task’s Exception property after the Task has
completed. Or it could be using a Task’s Result property.
If a Task sees that its exception has been observed in some manner,
life is good. If, however, all references to a Task are removed
(making the Task available for garbage collection), and if its
exception hasn’t yet been observed, the Task knows that its exception
will never be observed. In such a case, the Task takes advantage of
finalization, and uses a helper object to propagate the unhandled
exception on the finalizer thread. With the behavior described
earlier, that exception on the finalizer thread will go unhandled and
invoke the default unhandled exception logic, which is to log the
issue and crash the process.
He also suggested two useful extension methods for handling exceptions in "fire-and-forget" tasks: one ignoring the exception and the other one immediately crashing the process:
public static Task IgnoreExceptions(this Task task)
{
task.ContinueWith(c => { var ignored = c.Exception; },
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.DetachedFromParent);
return task;
}
public static Task FailFastOnException(this Task task)
{
task.ContinueWith(c => Environment.FailFast(“Task faulted”, c.Exception),
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.DetachedFromParent);
return task;
}
In .NET 4.5 the default behavior has changed. Again, a quote from another Stephen Toub's post on the subject (thanks to mike z for bringing it to my attention in the comments):
To make it easier for developers to write asynchronous code based on
Tasks, .NET 4.5 changes the default exception behavior for unobserved
exceptions. While unobserved exceptions will still cause the
UnobservedTaskException event to be raised (not doing so would be a
breaking change), the process will not crash by default. Rather, the
exception will end up getting eaten after the event is raised,
regardless of whether an event handler observes the exception. This
behavior can be configured, though.
Please note the code above is not quite correct. You must return the pointer to the task.ContinueWith, not the passed-in task:
public static Task IgnoreExceptions(this Task task)
{
var t = task.ContinueWith(c => { var ignored = c.Exception; },
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
return t;
}
EDIT:
This is challenging because it depends on how you chain your calls together. For example, the following call doesn't work the way I would expect:
public Task MyServiceCall()
{
return Task.Run(() => DoSomething()).IgnoreExceptions();
}
This method does indeed throw exceptions because the the accepted answer returns the initial task (not the one which observes the exception). This could be problematic with other calls, such as .Wait, .WhenAll etc. One might think that the task will never throw, but it can.
However, my suggested change would break the following:
public void SomeMethod()
{
var myTask = new Task(() => ...);
myTask.IgnoreExceptions().Start();
}
Internally we've decided to obsolete this extension method as it's too confusing!

Task and exception silence

Why exceptions thrown within a task are silent exception and you never know if a certain exception has been thrown
try
{
Task task = new Task(
() => {
throw null;
}
);
task.Start();
}
catch
{
Console.WriteLine("Exception");
}
the program run successfully in a complete silence!
where the behavior of threads is different
try
{
Thread thread = new Thread(
() => {
throw null;
}
);
thread .Start();
}
catch
{
Console.WriteLine("Exception");
}
a null pointer exception will be thrown in this case.
What is the difference?
The behaviour of that scenario depends on what framework you have; in 4.0, you actually need to be careful - if you don't handle TaskScheduler.UnobservedTaskException, it will error later when it gets collected/finalized, and will kill your process.
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
Trace.WriteLine(args.Exception.Message); // somebody forgot to check!
args.SetObserved();
};
This changes in 4.5, IIRC.
To check the outcome of a Task that might fail, you could register a continuation - i.e. call ContinueWith and check the result's exception. Alternatively, accessing the .Result of the task (which would also do an implicit Wait()) will re-surface the exception that happened. It is good to observe the result of a task, as that clears the finalization flag, meaning it can be collected more cheaply.
No, tasks are not threads. Tasks represent a high level abstraction - they are a unit of work that is inherently parallelisable. Threads run units of work.
In your first example, you create a unit of work and then tell it to run itself (how it does so is an implementation detail of Task). Whereas in your second example you explicitly schedule a unit of work (it would appear in a different manner to the implementation of Task).
The following assumes .NET 4.0, and the using the default TaskScheduler.
First of all notice that the exceptions are raised inside the delegates you pass, so they are raised in a different thread, not in the one you (logically) are doing your catch. So somehow the exception must be propagated from the thread that executes your delegate / lambda code to the one that started the thread/task.
Note that for Task, I think that the library can also choose not to run it on it's own thread, but rather on the calling thread (but I'm not sure if that was only true for Parallel.ForEach, etc. and not for a "naked" Task object).
For that two happen, two things must be fulfilled:
The calling thread is still active. Otherwise there is nothing left that could actually perform the catch.
The exception that was caused in the thread/task must somehow be preserved and reraised for you to catch it.
Having that said, both of your examples don't wait for the thread/task to finish. In the first example you're missing a task.Wait() (or similar) and in the second a thread.Join(). Depending on your test codes timing behavior this may mean that you may never be able to observe the exception from the thread/task (item 1 above).
Even if you add the two calls this is what happens for me (again .NET 4.0):
Task example: the call to task.Wait() actually reraises the exception originally left unhandled in the task's delegate (this is what the TPL will do for you internally), it does wrap it inside a System.AggregateException, which you could/would see if you'd use something more precise then a flat "catch-all".
Thread example: the exception raised by the delegate remains unhandled and your application exits (unless you do anything to deal with unhandled exceptions differently)
In other words I would have the examples as follows:
// Thread example
var thread = new Thread(() => { throw null; });
thread.Start();
thread.Join();
// Should never reach here, depending on timing sometimes not even
// until the Join() call. The process terminates as soon as the other
// thread runs the "throw null" code - which can logically happen somewhere
// after the "start" of the "Start()" call.
// Task example
try
{
var task = new Task(() => { throw null; });
task.Start();
task.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine("Exception: " + ex);
}
I think you are not getting exception in case of Task because you are not waiting for exception in the main thread. You are just continuing. Put task.Wait() and you will get the exception in main thread.

A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was

What does this mean and how to resolve it?
I am using TPL tasks.
The whole error
A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.
at System.Threading.Tasks.TaskExceptionHolder.Finalize()
mscorlib
If you create a Task, and you don't ever call task.Wait() or try to retrieve the result of a Task<T>, when the task is collected by the garbage collector, it will tear down your application during finalization. For details, see MSDN's page on Exception Handling in the TPL.
The best option here is to "handle" the exception. This can be done via a continuation - you can attach a continuation to the task, and log/swallow/etc the exception that occurs. This provides a clean way to log task exceptions, and can be written as a simple extension method, ie:
public static void LogExceptions(this Task task)
{
task.ContinueWith( t =>
{
var aggException = t.Exception.Flatten();
foreach(var exception in aggException.InnerExceptions)
LogException(exception);
},
TaskContinuationOptions.OnlyOnFaulted);
}
With the above, you can prevent any task from tearing down the app, and logging it, via:
Task.Factory.StartNew( () =>
{
// Do your work...
}).LogExceptions();
Alternatively, you can subscribe to the TaskScheduler.UnobservedTaskException and handle it there.
Sure; it means a Task got finalized after being left to garbage collection, but the task itself failed. There are two fixes:
handle the tasks fail directly (use ContinueWith(...) to subscribe, and check .IsFaulted and .Exception on the Task in the parameter)
handle the TaskScheduler.UnobservedTaskException event, and mark it observed (call e.SetObserved() after logging the error)

Categories

Resources