Is this correct way to handle an exception? - c#

I've argued with my colleague about handling exceptions in Task.Run blocks.
For example, I have this code:
private static async Task MainAsync()
{
try
{
await Task.Run(() => throw new Exception());
}
catch (Exception)
{
Console.WriteLine("oops");
}
}
If I run this code (in debug mode), I'll get the message from vs2019 about the unhandled exception. But if I press the continue button, the app will work correctly and the exception will be handled.
Is it the correct way to catch exceptions from Task.Run?

Generally, exception in tasks (objects of class Task) are placed on tasks, i.e. if some code in a task throws exception inside that task - it is stored in Exception property in Task class.
await operator automatically "unpacks" exception and throws. So your way is totally correct, but I think stack trace might change - so you need to keep that in mind.

Related

Catching Exceptions in async methods when not called with await

Goal:
I am confused by the behavior I am seeing with exceptions in my .Net Core library. The goal of this question is to understand why it is doing what I am seeing.
Executive Summary
I thought that when an async method is called, the code in it is executed synchronously until it hits the first await. If that is the case, then, if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)
Example Code:
Given the following code in a .Net Core Console Application:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
try
{
NonAwaitedMethod();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
Console.ReadKey();
}
public static async Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
await runTask;
}
public static async Task DoStuff(Action action)
{
// Simulate starting up blocking
var blocking = 100000;
await Task.Delay(500 + blocking);
action();
// Do the rest of the stuff...
await Task.Delay(3000);
}
}
Scenarios:
When run as is, this code will throw an exception, but, unless you have a break point on it, you will not know it. The Visual Studio Debugger nor the Console will give any indication that there was an issue (aside from a one line note in the Output screen).
Swap the return type of NonAwaitedMethod from Task to void. This will cause the Visual Studio Debugger to now break on the exception. It will also be printed out in the console. But notably, the exception is NOT caught in the catch statement found in Main.
Leave the return type of NonAwaitedMethod as void, but take off the async. Also change the last line from await runTask; to runTask.Wait(); (This essentially removes any async stuff.) When run, the exception is caught in the catch statement in the Main method.
So, to summarize:
| Scenario | Caught By Debugger | Caught by Catch |
|------------|--------------------|-----------------|
| async Task | No | No |
| async void | Yes | No |
| void | N/A | Yes |
Questions:
I thought that because the exception was thrown before an await was done, that it would execute synchronously up to, and through the throwing of the exception.
Hence my question of: Why does neither scenario 1 or 2 get caught by the catch statement?
Also, why does swapping from Task to void return type cause the exception to get caught by the Debugger? (Even though I am not using that return type.)
exception was thrown before an await was done, that it would execute synchronously
Thought this is fairly true, but it doesn't mean you could catch the exception.
Because your code has async keyword, which turns the method into an async state machine i.e. encapsulated / wrapped by a special type. Any exception thrown from async state machine will get caught and re-thrown when the task is awaited (except for those async void ones) or they go unobserved, which can be caught in TaskScheduler.UnobservedTaskException event.
If you remove async keyword from the NonAwaitedMethod method, you can catch the exception.
A good way to observe this behavior is using this:
try
{
NonAwaitedMethod();
// You will still see this message in your console despite exception
// being thrown from the above method synchronously, because the method
// has been encapsulated into an async state machine by compiler.
Console.WriteLine("Method Called");
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
So your code is compiled similarly to this:
try
{
var stateMachine = new AsyncStateMachine(() =>
{
try
{
NonAwaitedMethod();
}
catch (Exception ex)
{
stateMachine.Exception = ex;
}
});
// This does not throw exception
stateMachine.Run();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
why does swapping from Task to void return type cause the exception to get caught
If the method returns a Task, the exception is caught by the task.
If the method is void, then the exception gets re-thrown from an arbitrary thread pool thread. Any unhandled exception thrown from thread pool thread will cause the app to crash, so chances are the debugger (or maybe the JIT debugger) is watching this sort of exceptions.
If you want to fire and forget but properly handle the exception, you could use ContinueWith to create a continuation for the task:
NonAwaitedMethod()
.ContinueWith(task => task.Exception, TaskContinuationOptions.OnlyOnFaulted);
Note you have to visit task.Exception property to make the exception observed, otherwise, task scheduler still will receive UnobservedTaskException event.
Or if the exception needs to be caught and processed in Main, the correct way to do that is using async Main methods.
if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)
Good question. And in fact, the early preview versions of async/await did have that behavior. But the language team decided that behavior was just too confusing.
It's easy enough to understand when you have code like this:
if (test)
throw new Exception();
await Task.Delay(TaskSpan.FromSeconds(5));
But what about code like this:
await Task.Delay(1);
if (test)
throw new Exception();
await Task.Delay(TaskSpan.FromSeconds(5));
Remember that await acts synchronously if its awaitable is already completed. So has 1 millisecond gone by by the time the task returned from Task.Delay is awaited? Or for a more realistic example, what happens when HttpClient returns a locally cached response (synchronously)? More generally, the direct throwing of exceptions during the synchronous part of the method tends to result in code that changes its semantics based on race conditions.
So, the decision was made to unilaterally change the way all async methods work so that all exceptions thrown are placed on the returned task. As a nice side effect, this brings their semantics in line with enumerator blocks; if you have a method that uses yield return, any exceptions will not be seen until the enumerator is realized, not when the method is called.
Regarding your scenarios:
Yes, the exception is ignored. Because the code in Main is doing "fire and forget" by ignoring the task. And "fire and forget" means "I don't care about exceptions". If you do care about exceptions, then don't use "fire and forget"; instead, await the task at some point. The task is how async methods report their completion to their callers, and doing an await is how calling code retrieves the results of the task (and observe exceptions).
Yes, async void is an odd quirk (and should be avoided in general). It was put in the language to support asynchronous event handlers, so it has semantics that are similar to event handlers. Specifically, any exceptions that escape the async void method are raised on the top-level context that was current at the beginning of the method. This is how exceptions also work for UI event handlers. In the case of a console application, exceptions are raised on a thread pool thread. Normal async methods return a "handle" that represents the asynchronous operation and can hold exceptions. Exceptions from async void methods cannot be caught, since there is no "handle" for those methods.
Well, of course. In this case the method is synchronous, and exceptions travel up the stack just like normal.
On a side note, never, ever use the Task constructor. If you want to run code on the thread pool, use Task.Run. If you want to have an asynchronous delegate type, use Func<Task>.
The async keyword indicates that the compiler should transform the method to an async state machine, which is not configurable regarding the handling of the exceptions. If you want the sync-part-exceptions of the NonAwaitedMethod method to be thrown immediately, there is no other option than removing the async keyword from the method. You can have the best of both worlds by moving the async part into an async local function:
public static Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
return ImlpAsync(); async Task ImlpAsync()
{
await runTask;
};
}
Instead of using a named function, you could also use an anonymous one:
return ((Func<Task>)(async () =>
{
await runTask;
}))();

How do I ensure an operation won't crash the whole app?

I have an application performing some additional jobs like cleaning old logs, sending notifications and so on. If one job fails I don't want the whole application to stop working and not perform jobs what left.
So for example,
await SendUsersBirthdayEmailsAsync(); // <-- if something fails while trying to send birthday emails here, I don't want the app to stop working and not clean logs and so on...
await DeleteOutdatedLogsAsync();
await SendSystemNotificationsAsync();
What would you recommend me to go with?
Use try-catch block on every part of the code that can fail.
Depending on what you need, use try-catch-finally block.
On every catch block, log the exception however you want. I use Nlog for logging so i suggest looking into that.
try{
//do work here
}
catch(Exception e){
//log exception here
}
//optional
finally{
//do optional needed work here
}
Something like this:
public bool SendUsersBirthdayEmailsAsync(){
try{
SendMail();
}
catch(Exception e){
LogException(e);
}
//optional
finally{
OptionalWork();
}
}
EDIT: About avoiding using generic exception
You can always use multiple catch blocks any define different behavior for each type of exception. This is useful when you know what kind of exception can be expected.
Example:
public bool SendUsersBirthdayEmailsAsync(){
try{
SendMail();
}
catch (ThreadAbortException tae)
{
LogException(tae);
//do something specific
}
catch (ThreadInterruptedException tie)
{
LogException(tie);
//do something specific
}
catch(Exception e){
LogException(e);
}
//optional
finally{
OptionalWork();
}
}
EDIT 2: Official Microsoft guidance for exception handling.
Use try/catch blocks around code that can potentially generate an exception and your code can recover from that exception. In catch blocks, always order exceptions from the most derived to the least derived. All exceptions derive from Exception. More derived exceptions are not handled by a catch clause that is preceded by a catch clause for a base exception class. When your code cannot recover from an exception, don't catch that exception. Enable methods further up the call stack to recover if possible.
Clean up resources allocated with either using statements, or finally blocks. Prefer using statements to automatically clean up resources when exceptions are thrown. Use finally blocks to clean up resources that don't implement IDisposable. Code in a finally clause is almost always executed even when exceptions are thrown.
Task<Task> task = (SendUsersBirthdayEmailsAsync()
.ContinueWith(x => Task.WhenAll(DeleteOutdatedLogsAsync(), SendSystemNotificationsAsync())));
await await task;
For more general sample the below code provided:
class TaskTest
{
public async void Start()
{
await (
(await One().ContinueWith(x => Task.WhenAll(Two(), Three())))
.ContinueWith(x=> Four()));
}
private async Task One()
{
await Task.Delay(5000);
Console.WriteLine("1");
throw new Exception();
}
private async Task Two()
{
await Task.Delay(2000);
Console.WriteLine("2");
throw new Exception();
}
private async Task Three()
{
await Task.Delay(3000);
Console.WriteLine("3");
throw new Exception();
}
private async Task Four()
{
await Task.Delay(1000);
Console.WriteLine("4");
throw new Exception();
}
}
running this code shows that throwing exceptions inside tasks does not halt the whole program.
If you are already doing it with a scheduler why not separate it into smaller tasks? In that way if one action fails it will not bring down all the others. Also it is a good practice to have many smaller applications with dedicated responsibilities rather than a generic one that does everything.
If you are looking for a reliable option to ensure the process is completed and is traceable, you can go with Hangfire. You can put your retry logic in case of failures by handling exceptions also.
Since the name of the methods suggests, that these methods are independent of each other, just make sure each returns an await-able task and put them into a list. Your use of await suggests, they already do.
Put one try-catch around it and catch the AggregateExeption.
List<Task> tasks = new List<Task>();
try
{
tasks.Add(SendUsersBirthdayEmailsAsync());
tasks.Add(DeleteOutdatedLogsAsync());
tasks.Add(SendSystemNotificationsAsync());
Task.WhenAll(tasks); // waits for all tasks to finish
}
catch (Exception e)
{
//Log e.ToString(); will give you all inner exceptions of the aggregate exception as well, incl. StackTraces, so expect possibly a lot of chars, wherever you log it.
}
This may also speed things up, because they are now running in parallel. But since they all seem to work on a database (most likely the same), it might not be much, if at all, because of the scheduling overhead.
You can implement your methods using the Try-Pattern and have them return Boolean.
Let every method handle exceptions on it's own so that you are sure no unhandled exceptions can be thrown to kill off your application.
In case a failure will result in unrecoverable program state, the methods should return false and your application should quit in a clean way by itself.
Unobserved Task Exceptions
await SendUsersBirthdayEmailsAsync(); // uses TaskScheduler
Since you are working with a TaskScheduler, check out TaskScheduler.UnobservedTaskException.
Occurs when a faulted task's unobserved exception is about to trigger
exception escalation policy, which, by default, would terminate the
process.
You need to use Try-Catch in every function as follow.
try{
//write your code
}
catch(Exception e){
//your exception will be here
}
You can generate a log file for that exception.
Just Do one thing for your log File. Create A Log Class and a log file generate function.
call that function in catch area of your all functions.
Let you create a Log class with name clsLog. and a static function with name InsertLog(string exception, string functionname).
Use this log method in your all function as follow.
public void insertcity()
{
try
{
//Insert city programming
}
catch (exception ex)
{
clsLog.InsertLog(ex.Message,"insertCity");
//do something else you want.
}
}
Hope it will help.
i think if u use
try{
//your nice work
}
catch(Exception e){
//hmm.. error ok i will show u
}
//optional
finally{
//again start the operation if u failed here.
}
i also make the same application in which when some error comes and failed then in the catch exception i make the log in system and also again restart the application in the finally. so it will never die.
I would recommend to found out what exception you getting from async to actually get this exception and understand it you can use on a main Thread:
MyTask().GetAwaiter().GetResult();
This will allow you to see the text of the actual exception that you have on your await Thread.
And a little bit if you don't want a main Thread to return to the line with await you also can use:
await SendUsersBirthdayEmailsAsync().ConfigureAwait(false);
This will just continue your Task on the nearest free Thread.
But need to say this will cause an exception on Android because it have some restrictions of using Threads.
In the case you don't want to waste your time or don't need to handle the exception manually and fixing it try, catch finally is always an option.
The answer that Andreas used - fits what I was thinking - however I would make one more piece - A Cancellation Token - with a time limit on how long you would think is enough time - so that everything completes.
The individual tasks in the Wait means that they all come back together at that point while they execute seperately. You can catch the agreggate exceptions if they occur - and then determine if it was timeout - or something you need to log.
The Two suggestions on UnhandledException and UnhandledTask Exception SHOULD be included also - for what ever reason that they stop..
I would also think about making this a Windows service so that you can pass the cancellation token into the tasks from startup - and have a timer on when the service actually runs - instead of a scheduled task - then with all the exception handling you can see what the log tells you over time - which feels a little more resilient than a console app to me - but that is how we normally run tasks in this vein.
You can start and stop a service like this in a console app for testing - and then hand it off into a project that handles the OnStart and OnStop events - passing the Cancellation token in - OnStop would cancel the token and stop the service loops.

Why the manual raised Transient Error exceptions are handled as an AggregateException?

When I try to raise transient exception manually, it is always handled as AggregateException. Since it is handled as AggregateException , it is not handled as transient error in my retry policy and not retried for the predefined retry count.
Transient errors are shown here .
Therefore I have tried CommunicationException and ServerErrorException but it is handled as an AggregateException.
When I look for AggregateException, it says "Represents one or more errors that occur during application execution." Yeah, it is so helpful!!!
Here is the example code of my case:
I have a retry policy which uses ServiceBusTransientErrorDetectionStrategy
public void TestManually()
{
var retryPolicy = new RetryPolicy<ServiceBusTransientErrorDetectionStrategy>(RetryStrategy.DefaultFixed);
retryPolicy.Retrying += (obj, eventArgs) =>
{
Trace.TraceError("Hey!! I'm Retrying, CurrentRetryCount = {0} , Exception = {1}", eventArgs.CurrentRetryCount, eventArgs.LastException.Message);
};
retryPolicy.ExecuteAsync(() =>
MyTestFunction().ContinueWith(t =>
{
if (t.Exception != null)
{
// A non-transient exception occurred or retry limit has been reached
Trace.TraceError("This was not a transient exxception... It was: " + t.Exception.GetType().ToString());
}
}));
}
public Task MyTestFunction()
{
Task task = Task.Factory.StartNew(() => RaiseTransientErrorManually());
return task;
}
public void RaiseTransientErrorManually()
{
//throw new CommunicationException();
throw new ServerErrorException();
}
Let's say I call my function like this:
TestManually();
I'm very confused why the manually thrown exception (which is defined as Transient Error) is handled as AggregateException ? What I'm missing there?
Thanks.
Exceptions within asynchronous code are a tricky subject for two reasons.
The manner in which exceptions are handled (e.g. by catch blocks) is not always intuitive, and may seem inconsistent.
The manner in which libraries document the behavior for exceptions thrown by asynchronous methods is not always obvious.
I'll address each of these items below.
Important Note: This answer uses the term asynchronous method to refer to any method with a return type of Task or Task<T>. Languages with built-in support for asynchronous programming have their own related terminology which may differ in meaning.
Exceptions Thrown by Asynchronous Methods
Asynchronous methods are capable of throwing exceptions before creating a Task or during the asynchronous execution of the task itself. While projects are not always consistent in the way exceptions are documented for asynchronous code, I like to include the following note with my projects to make things clear for my users.
Note: only assume the following quote is true for a library which explicitly states it. The statement is specifically meant to address the second problem area described above.
The documentation for asynchronous methods does not distinguish between these two cases, allowing for any of the specified exceptions to be thrown in either manner.
Exceptions Prior to Task Creation
Exceptions thrown prior to the creation of the Task object representing the asynchronous operation must be caught directly by the calling code. For example, if the code throws an ArgumentNullException in this manner, the calling code would need to contain an exception handler for ArgumentNullException or ArgumentException to handle the exception.
Example code which throws a direct exception:
public Task SomeOperationAsync()
{
throw new ArgumentException("Directly thrown.");
}
Example code which handles a directly-thrown exception:
try
{
Task myTask = SomeOperationAsync();
}
catch (ArgumentException ex)
{
// ex was thrown directly by SomeOperationAsync. This cannot occur if
// SomeOperationAsync is an async function (§10.15 - C# Language Specification
// Version 5.0).
}
Exceptions During Task Execution
Exceptions thrown during the asynchronous execution of the task are wrapped in an AggregateException object and returned by the Exception property. Exceptions thrown in this manner must be handled either by a task continuation that checks the Exception property, or by calling Wait or checking the Result property within an exception handling block that includes a handler for AggregateException.
In libraries that I create, I provide an additional guarantee for users which reads as follows:
Note: only assume the following quote is true for a library which explicitly states it.
This library additionally ensures that exceptions thrown by asynchronous operations are not wrapped in multiple layers of AggregateException. In other words, an ArgumentException thrown during the asynchronous execution of a task will result in the Exception property returning an AggregateException, and that exception will not contain any nested instances of AggregateException in the InnerExceptions collection. In most cases, the AggregateException wraps exactly one inner exception, which is the original ArgumentException. This guarantee simplifies the use of the API is languages that support async/await, since those operators automatically unwrap the first layer of AggregateException.
Example methods which each throw an exception during task execution:
public Task SomeOperationAsync()
{
return Task.StartNew(
() =>
{
throw new ArgumentException("Directly thrown.");
});
}
public async Task SomeOtherOperationAsync()
{
throw new ArgumentException("async functions never throw exceptions directly.");
}
Example code which handles an exception during task execution:
try
{
Task myTask = SomeOperationAsync();
myTask.Wait();
}
catch (AggregateException wrapperEx)
{
ArgumentException ex = wrapperEx.InnerException as ArgumentException;
if (ex == null)
throw;
// ex was thrown during the asynchronous portion of SomeOperationAsync. This is
// always the case if SomeOperationAsync is an async function (§10.15 - C#
// Language Specification Version 5.0).
}
Consistent Exception Handling
Applications implementing specialized handling for exception which occur during asynchronous calls have multiple options available for consistent handling. The simplest solution, when available, involves using async/await. These operators automatically unwrap the first exception instance in the InnerExceptions collection of an AggregateException, resulting in behavior that appears to calling code as though the exception was directly thrown by the invoked method. The second method involves treating the original call as a continuation of another task, ensuring that all exceptions are presented as an AggregateException to the exception handling code. The following code shows the application of this strategy to an existing asynchronous call. Note that the CompletedTask class and Then() extension method are part of the separate Rackspace Threading Library (open-source, Apache 2.0).
// original asynchronous method invocation
Task task1 = SomeOperationAsync();
// method invocation treated as a continuation
Task task2 = CompletedTask.Default.Then(_ => SomeOperationAsync());
Code using the continuation strategy for consistent error handling may benefit from the use of the Catch() methods, which are also part of the Rackspace Threading Library. This extension method behaves in a manner similar to await, automatically unwrapping the first exception instance in the InnerExceptions collection of an AggregateException before invoking the continuation function which handles the exception.
As of Transient Fault Handling v6.0.1304.0 , the following code successfully retries as per the configured detection strategy:
Strategy:
public class SimpleHandlerStartegy : ITransientErrorDetectionStrategy
{
public bool IsTransient(Exception ex)
{
if (ex is WebException)
{
return true;
}
return false;
}
}
Code That Throws WebException:
async Task<int> SomeAsyncWork()
{
await Task.Delay(1000);
throw new WebException("This is fake");
return 1; // Unreachable!!
}
Client Code:
var retryStrategy = new Incremental(3, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
var retryPolicy = new RetryPolicy<SimpleHandlerStartegy>(retryStrategy);
retryPolicy.Retrying += (sender, retryArgs) =>
{
Console.WriteLine("Retrying {0}, Delay{1}, Last Exception: {2}", retryArgs.CurrentRetryCount, retryArgs.Delay, retryArgs.LastException);
};
// In real world, await this to get the return value
retryPolicy.ExecuteAsync(() => SomeAsyncWorkThatThrows());
As far as I understand, exceptions that are raised within an asynchronous code block are delivered back to the main thread within an aggregate exception. I suppose this is because raising an exception doesn't necessarily cause execution to be returned to the main thread and therefore we could have more than one exception returned.

Console application - try catch block doesn't work for Tasks

It's probably a basic question but when I try to catch an exception thrown in a Task (in a console application) the app breaks with unhandled exception even if I call task.Wait() within a try catch block (adding something like Thread.Sleep before throwing doesn't help):
static void Main(string[] args)
{
Task task = Task.Run(() => { throw null; });
try
{
task.Wait();
}
catch (AggregateException ae)
{
if (ae.InnerException is NullReferenceException)
Console.WriteLine("Null!");
else
Console.WriteLine("Exception!");
}
Console.ReadLine();
}
Screenshots:
If you put a breakpoint in your catch and continue execution you'll see that the exception will be caught.
If you await for your task then you'll have your exception. Otherwise it would be happening in another thread.
This screen only shows on the point of the break point what is happening if you continue execution of the program or move the break point further down you will see that the exception is catch and returns Null as expected
And the simplest explanation from debugger point of view will be - I see exception so now what? ooo here is the catch statement lets see what to do with it
uncheck this line Break when this exception type is user-unhandled and click on continue.

How can I make VS break on exceptions in an async Task, without breaking on all exceptions?

As indicated here and here, exceptions occuring in an async Task are technically not unhandled.
This is particularly nasty when working with MVC. It actually took us a while to figure out why it was occurring more and more that exceptions weren't being caught, we had been gradually introducing Web API calls to our aplication the past few weeks.
public async Task<ActionResult> Foo()
{
// ...
}
The suggested workaround is to make VS break on all exceptions instead of only unhandled exceptions. It works, with the annoying 'side-effect' that it does indeed break on all exceptions :)
Is there another workaround that doesn't involve breaking on all exceptions? It can be specific to MVC but doesn't have to be (meaning if it's a general solution that happens to work for MVC).
A) Wrap your calls and throw a custom Exception in your Task code. Break on only throw of your custom exception. You can select the Exceptions for first throw.
B). Debug.Assert() your Task Results, if you have any wait code. i.e., not just firing and forgetting. Tasks return the Exceptions in a property if you wait on them somewhere or stick error handling in a continuation.
psuedo code
i.e. task. continuewith(r => if(!r.Exception is null) Debug.Break()))
etc.
Hope that helps you on the right path.
You could try and listen for this event if nothing else works here
AppDomain.CurrentDomain.UnhandledException
or
AppDomain.CurrentDomain.FirstChanceException
Then you need to put some if-constructs (check what sender for example) to hit your breakpoint only when it makes sense.
The try-catch reference in MSDN has some guidance and examples on exceptions in async methods, and states: "The completed task to which await is applied might be in a faulted state because of an unhandled exception in the method that returns the task. Awaiting the task throws an exception."
For the example given on that page, it states: "The following example illustrates exception handling for async methods. To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block. Uncomment the throw new Exception line in the example to demonstrate exception handling. The task's IsFaulted property is set to True, the task's Exception.InnerException property is set to the exception, and the exception is caught in the catch block."
Here's the copy from the example given there:
public async Task DoSomethingAsync()
{
Task<string> theTask = DelayAsync();
try
{
string result = await theTask;
Debug.WriteLine("Result: " + result);
}
catch (Exception ex)
{
Debug.WriteLine("Exception Message: " + ex.Message);
}
Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
Debug.WriteLine("Task IsFaulted: " + theTask.IsFaulted);
if (theTask.Exception != null)
{
Debug.WriteLine("Task Exception Message: "
+ theTask.Exception.Message);
Debug.WriteLine("Task Inner Exception Message: "
+ theTask.Exception.InnerException.Message);
}
}
private async Task<string> DelayAsync()
{
await Task.Delay(100);
// Uncomment each of the following lines to
// demonstrate exception handling.
//throw new OperationCanceledException("canceled");
//throw new Exception("Something happened.");
return "Done";
}
// Output when no exception is thrown in the awaited method:
// Result: Done
// Task IsCanceled: False
// Task IsFaulted: False
// Output when an Exception is thrown in the awaited method:
// Exception Message: Something happened.
// Task IsCanceled: False
// Task IsFaulted: True
// Task Exception Message: One or more errors occurred.
// Task Inner Exception Message: Something happened.
// Output when a OperationCanceledException or TaskCanceledException
// is thrown in the awaited method:
// Exception Message: canceled
// Task IsCanceled: True
// Task IsFaulted: False
Enabling "Just My Code" is one way to do this, as suggested here. True, the exceptions are technically not unhandled, but they are unhandled by user code, which gets VS to do the useful thing and show you where the exception occurred.
See this answer for some screenshots.
Obviously this setting has other implications (e.g. you can no longer step through framework code), so this may or may not be suitable.
You could add a handler for TaskScheduler.UnobservedTaskException.
However, perhaps you've already tried that and you want a way to make the debugger break at the original exception rather than in the unobserved task exception handler.

Categories