Exception Handling in async/await in C # (below)

Source: Internet
Author: User

Previous Article Workshop. The WhenAll auxiliary method summarizes a series of task objects. Once an error occurs, an "one of them" exception is thrown. So what is the exception? What if we want to handle all exceptions? This time, we will discuss in detail the relevant behaviors of await operations during abnormal dispatch.
Await throws an exception
To understand await behaviors, you must understand the abnormal performance of the Task object. The Task object has an Exception attribute whose type is aggresponexception. If the execution is successful, null is returned for this attribute. Otherwise, all objects with errors are included. Since it is aggresponexception, it means that it may contain multiple sub-exceptions. This situation often occurs in the parent-child relationship of the task. For details, refer to the relevant description in MSDN. In many cases, only one exception occurs within a Task. In this case, the InnerExceptions attribute of this aggresponexception is naturally only one element.
The Task object itself has a Wait method that blocks the current code execution until the Task is completed. When an exception occurs, it will throw its own aggresponexception:
Try
{
T. Wait ();
}
Catch (aggresponexception ex)
{
...
}
The Wait method is "True blocking", while the await operation uses the Code with blocking semantics to achieve non-blocking effect. The difference must be distinguished. Unlike the Wait method, the effect of the await operator is not the Exception attribute of the "throw" Task object, but the "one" element of the aggresponexception object. I asked about the design considerations in the internal email list. C # developers replied that the decision had undergone heated internal debates, instead of directly throwing aggresponexception on the Task object, the final choice is to avoid coding redundant code and bring the Code closer to the traditional Synchronous Programming habits.
They provide a simple example. If a Task object t may throw two types of exceptions, the current error capture method is:
Try
{
Await t1;
}
Catch (NotSupportedException ex)
{
...
}
Catch (NotImplementedException ex)
{
...
}
Catch (Exception ex)
{
...
}
If the await operation throws an aggresponexception, the Code must be written as follows:
Try
{
Await t1;
}
Catch (aggresponexception ex)
{
Var innerEx = ex. InnerExceptions [0];

If (innerEx is NotSupportedException)
{
...
}
Else if (innerEx is NotImplementedException)
{
...
}
Else
{
...
}
}
Obviously, the former is closer to the traditional Synchronous Programming habits. But the problem is, what if this Task contains multiple exceptions? The previous description throws an "one" exception. for developers, the vague statement "one" is naturally unsatisfactory, but it is true. From the discussion in the internal email list, the C # Development Team mentioned that they intentionally did not provide a document to indicate which exception will be thrown, because they do not want to make such constraints, this is because this part of behavior becomes a constraint once written into the document and cannot be modified for the compatibility of the class library in the future.
They also mentioned that if we talk about the current implementation, await operations will be performed from the Task. exception. in the InnerExceptions set, pick out the first exception and "throw" it. This is System. runtime. compilerServices. actions defined in the TaskAwaiter class. However, since this is not a "documented" fixed behavior, developers should not rely on this as much as possible.
WhenAll exception summary Method
In fact, this topic has no connection with async/await actions. WhenAll returns a common Task object, and TaskAwaiter does not care whether the currently waiting Task object comes from WhenAll, however, since WhenAll is one of the most commonly used auxiliary methods, it is also explained clearly by the way.
WhenAll obtains the Task object. The result is the result of all sub-tasks stored in arrays. In case of an Exception, the aggresponexception set returned by the Exception attribute contains the exceptions thrown by all sub-tasks. Note that the exception thrown by each subtask is stored in its own aggresponexception set. The Task object returned by WhenAll will collect the elements in each aggresponexception set in order, instead of collecting every aggresponexception object.
We use a simple example to understand this:
Task all = null;
Try
{
Await (all = Task. WhenAll (
Task. WhenAll (
ThrowAfter (3000, new Exception ("Ex3 ")),
ThrowAfter (1000, new Exception ("Ex1 "))),
ThrowAfter (2000, new Exception ("Ex2 "))));
}
Catch (Exception ex)
{
...
}
This Code uses the nested WhenAll method. There are three exceptions in total, which are sorted by the time they are thrown. The order is Ex1, Ex2, and Ex3. Excuse me:
1. Which exception is caught by the catch statement?
2. What are the exceptions in the aggresponexception set of all. Exception in order?
The result is as follows:
1. the exception caught by the catch statement is Ex3 because it is all. exception is the first element in the aggresponexception set, but keep this in mind. This is only the behavior implemented by the current TaskAwaiter, not the result specified by the document.
2. all. Exception: There are three exceptions in the aggresponexception set, which are Ex3, Ex1, and Ex2 in order. The Task object obtained by WhenAll determines the storage sequence of abnormal objects in the AggreagteException set according to the order of the input Task objects. This sequence has nothing to do with the time when an exception is thrown.
By the way, if you do not want to catch one of the exceptions in the aggresponexception set but want to handle all exceptions, you can also write the following code:
Task all = null;
Try
{
Await (all = Task. WhenAll (
ThrowAfter (1000, new Exception ("Ex1 ")),
ThrowAfter (2000, new Exception ("Ex2 "))));
}
Catch
{
Foreach (var ex in all. Exception. InnerExceptions)
{
...
}
}
Of course, Task is used here. whenAll is used as an example because the Task object can explicitly contain multiple exceptions, but not only tasks. the Task object returned by WhenAll may contain multiple exceptions. For example, if the parent-child relationship is specified when the Task object is created, the parent Task contains exceptions in each subtask.
If the exception is not captured
Finally, let's take a look at a simple question. We have been paying attention to the "capture" exception behavior in an async method. If the exception is not successfully captured and thrown out, what is the impact on the task itself? See this example:
Static async Task SomeTask ()
{
Try
{
Await Task. WhenAll (
ThrowAfter (2000, new NotSupportedException ("Ex1 ")),
ThrowAfter (1000, new NotImplementedException ("Ex2 ")));
}
Catch (NotImplementedException ){}
}

Static void Main (string [] args)
{
_ Watch. Start ();

SomeTask (). ContinueWith (t => PrintException (t. Exception ));

Console. ReadLine ();
}
The output result of this Code is:
System. aggresponexception: One or more errors occurred. ---> System. NotSupportedException: Ex1
At AsyncErrorHandling. Program. d _ 0. MoveNext () in... \ Program. cs: line 16
--- End of stack trace from previous location where exception was thrown ---
At System. Runtime. CompilerServices. TaskAwaiter. ThrowForNonSuccess (Task task)
At System. Runtime. CompilerServices. TaskAwaiter. HandleNonSuccessAndDebuggerNotification (Task task)
At System. Runtime. CompilerServices. TaskAwaiter. GetResult ()
At AsyncErrorHandling. Program. d _ 3. MoveNext () in... \ Program. cs: line 30
--- End of inner exception stack trace ---
---> (Inner Exception #0) System. NotSupportedException: Ex1
At AsyncErrorHandling. Program. d _ 0. MoveNext () in... \ Program. cs: line 16
--- End of stack trace from previous location where exception was thrown ---
At System. Runtime. CompilerServices. TaskAwaiter. ThrowForNonSuccess (Task task)
At System. Runtime. CompilerServices. TaskAwaiter. HandleNonSuccessAndDebuggerNotification (Task task)
At System. Runtime. CompilerServices. TaskAwaiter. GetResult ()
At AsyncErrorHandling. Program. d _ 3. MoveNext () in... \ Program. cs: line 30 <---
The printed content of aggresponexception is not so easy to read. We can pay attention to information like Inner Exception #0. In terms of time, Ex2 is thrown before Ex1, And the catch target is NotImplementedException. However, we can see from the previous descriptions that the exception set in the Task returned by WhenAll has no relationship with the time when each exception is thrown. Therefore, the await operator throws Ex1 and NotSupportedException, it will not be cached, so the Task object returned by SomeTask will also contain this exception-it only throws this exception, and Ex2 is invisible to the outside.
If you want to handle all exceptions externally, you can:
Task all = null;
Try
{
Await (all = Task. WhenAll (
ThrowAfter (2000, new NotSupportedException ("Ex1 ")),
ThrowAfter (1000, new NotImplementedException ("Ex2 "))));
}
Catch
{
Throw all. Exception;
}
The printed result is an aggresponexception that contains another aggresponexception, including Ex1 and Ex2. To "Unbind" the nested relationship, aggresponexception also provides a Flatten method to completely "Flatten" the nesting, for example:
SomeTask (). ContinueWith (t => PrintException (t. Exception. Flatten ()));
The printed result is an aggresponexception that contains Ex1 and Ex2.

 
From Lao Zhao's point of view-pursuing the beauty of Programming

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.