├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── merge-dependabot.yml ├── .gitignore ├── AssemblyTemplate ├── AssemblyTemplate.csproj ├── AsyncErrorHandler.cs └── Template.cs ├── AssemblyToProcess ├── AssemblyToProcess.csproj ├── AsyncErrorHandler.cs └── Target.cs ├── AssemblyWithHandlerInReference ├── AssemblyWithHandlerInReference.csproj ├── Class1.cs └── Target.cs ├── AsyncErrorHandler.Fody ├── AsyncErrorHandler.Fody.csproj ├── HandleMethodFinder.cs ├── MethodProcessor.cs ├── ModuleWeaver.cs ├── StateMachineChecker.cs └── StateMachineTypesFinder.cs ├── AsyncErrorHandler.sln ├── AsyncErrorHandler ├── AsyncErrorHandler.csproj └── key.snk ├── CommonAssemblyInfo.cs ├── Directory.Build.props ├── License.txt ├── Tests ├── AssemblyExtensions.cs ├── InSameAssemblyTests.cs ├── Tests.csproj └── WithHandlerInReferenceTests.cs ├── appveyor.yml ├── global.json ├── key.snk ├── package_icon.png └── readme.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | 9 | [*.cs] 10 | indent_size = 4 11 | 12 | # Sort using and Import directives with System.* appearing first 13 | dotnet_sort_system_directives_first = true 14 | 15 | # Avoid "this." and "Me." if not necessary 16 | dotnet_style_qualification_for_field = false:error 17 | dotnet_style_qualification_for_property = false:error 18 | dotnet_style_qualification_for_method = false:error 19 | dotnet_style_qualification_for_event = false:error 20 | 21 | # Use language keywords instead of framework type names for type references 22 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 23 | dotnet_style_predefined_type_for_member_access = true:error 24 | 25 | # Suggest more modern language features when available 26 | dotnet_style_object_initializer = true:suggestion 27 | dotnet_style_collection_initializer = true:suggestion 28 | dotnet_style_coalesce_expression = false:suggestion 29 | dotnet_style_null_propagation = true:suggestion 30 | dotnet_style_explicit_tuple_names = true:suggestion 31 | 32 | # Prefer "var" everywhere 33 | csharp_style_var_for_built_in_types = true:error 34 | csharp_style_var_when_type_is_apparent = true:error 35 | csharp_style_var_elsewhere = true:error 36 | 37 | # Prefer method-like constructs to have a block body 38 | csharp_style_expression_bodied_methods = false:none 39 | csharp_style_expression_bodied_constructors = false:none 40 | csharp_style_expression_bodied_operators = false:none 41 | 42 | # Prefer property-like constructs to have an expression-body 43 | csharp_style_expression_bodied_properties = true:suggestion 44 | csharp_style_expression_bodied_indexers = true:suggestion 45 | csharp_style_expression_bodied_accessors = true:none 46 | 47 | # Suggest more modern language features when available 48 | csharp_style_pattern_matching_over_is_with_cast_check = true:error 49 | csharp_style_pattern_matching_over_as_with_null_check = true:error 50 | csharp_style_inlined_variable_declaration = true:suggestion 51 | csharp_style_throw_expression = true:suggestion 52 | csharp_style_conditional_delegate_call = true:suggestion 53 | 54 | # Newline settings 55 | #csharp_new_line_before_open_brace = all:error 56 | csharp_new_line_before_else = true 57 | csharp_new_line_before_catch = true 58 | csharp_new_line_before_finally = true 59 | csharp_new_line_before_members_in_object_initializers = true 60 | csharp_new_line_before_members_in_anonymous_types = true 61 | 62 | #braces 63 | #csharp_prefer_braces = true:error 64 | 65 | # msbuild 66 | [*.{csproj,targets,props}] 67 | indent_size = 2 68 | 69 | # Xml files 70 | [*.{xml,config,nuspec,resx,vsixmanifest}] 71 | indent_size = 2 72 | resharper_xml_wrap_tags_and_pi = true:error 73 | 74 | # JSON files 75 | [*.json] 76 | indent_size = 2 77 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text 3 | 4 | # Don't check these into the repo as LF to work around TeamCity bug 5 | *.xml -text 6 | *.targets -text 7 | 8 | # Custom for Visual Studio 9 | *.cs diff=csharp 10 | *.sln merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | 16 | # Denote all files that are truly binary and should not be modified. 17 | *.dll binary 18 | *.exe binary 19 | *.png binary 20 | *.ico binary 21 | *.snk binary 22 | *.pdb binary 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: merge-dependabot 2 | on: 3 | pull_request: 4 | jobs: 5 | automerge: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - name: Dependabot Auto Merge 10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6 11 | with: 12 | target: minor 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | command: squash and merge -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Visual Studio 3 | ################# 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.sln.docstates 12 | *.pidb 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | *_i.c 19 | *_p.c 20 | *.ilk 21 | *.meta 22 | *.obj 23 | *.pch 24 | *.pgc 25 | *.pgd 26 | *.rsp 27 | *.sbr 28 | *.tlb 29 | *.tli 30 | *.tlh 31 | *.tmp 32 | *.vspscc 33 | .builds 34 | *.dotCover 35 | 36 | *.DotSettings 37 | *.ncrunchsolution 38 | 39 | ## If you have NuGet Package Restore enabled, uncomment this 40 | packages/ 41 | ForSample/ 42 | nugets/ 43 | 44 | # Visual Studio profiler 45 | *.psess 46 | *.vsp 47 | 48 | # ReSharper is a .NET coding add-in 49 | _ReSharper* 50 | 51 | # Others 52 | [Bb]in 53 | [Oo]bj 54 | sql 55 | TestResults 56 | *.Cache 57 | ClientBin 58 | stylecop.* 59 | ~$* 60 | *.dbmdl 61 | Generated_Code #added for RIA/Silverlight projects 62 | 63 | # Backup & report files from converting an old project file to a newer 64 | # Visual Studio version. Backup files are not needed, because we have git ;-) 65 | _UpgradeReport_Files/ 66 | Backup*/ 67 | UpgradeLog*.XML 68 | 69 | 70 | ############ 71 | ## Windows 72 | ############ 73 | 74 | # Windows image file caches 75 | Thumbs.db 76 | 77 | # Folder config file 78 | Desktop.ini 79 | 80 | .vs 81 | .idea/ 82 | -------------------------------------------------------------------------------- /AssemblyTemplate/AssemblyTemplate.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | -------------------------------------------------------------------------------- /AssemblyTemplate/AsyncErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | public static class AsyncErrorHandler 5 | { 6 | public static void HandleException(Exception exception) 7 | { 8 | Debug.WriteLine(exception); 9 | } 10 | } -------------------------------------------------------------------------------- /AssemblyTemplate/Template.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | public class Template 4 | { 5 | public async Task Method() 6 | { 7 | await Task.Delay(1); 8 | } 9 | 10 | public async Task MethodWithThrow() 11 | { 12 | await Task.Delay(1); 13 | throw new(); 14 | } 15 | 16 | public async Task MethodGeneric() 17 | { 18 | await Task.Delay(1); 19 | return 1; 20 | } 21 | 22 | public async Task MethodWithThrowGeneric() 23 | { 24 | await Task.Delay(1); 25 | throw new(); 26 | } 27 | } -------------------------------------------------------------------------------- /AssemblyToProcess/AssemblyToProcess.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | -------------------------------------------------------------------------------- /AssemblyToProcess/AsyncErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public static class AsyncErrorHandler 4 | { 5 | public static Exception Exception; 6 | public static void HandleException(Exception exception) 7 | { 8 | Exception = exception; 9 | } 10 | } -------------------------------------------------------------------------------- /AssemblyToProcess/Target.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | public class Target 4 | { 5 | public async Task Method() 6 | { 7 | await Task.Delay(1); 8 | } 9 | public async Task MethodWithThrow() 10 | { 11 | await Task.Delay(1); 12 | throw new(); 13 | } 14 | public async Task MethodGeneric() 15 | { 16 | await Task.Delay(1); 17 | return 1; 18 | } 19 | public async Task MethodWithThrowGeneric() 20 | { 21 | await Task.Delay(1); 22 | throw new(); 23 | } 24 | } -------------------------------------------------------------------------------- /AssemblyWithHandlerInReference/AssemblyWithHandlerInReference.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /AssemblyWithHandlerInReference/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class Class1 4 | { 5 | // ReSharper disable once UnusedMember.Local 6 | Type x = typeof (AsyncErrorHandler); 7 | } -------------------------------------------------------------------------------- /AssemblyWithHandlerInReference/Target.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | public class Target 4 | { 5 | public async Task Method() 6 | { 7 | await Task.Delay(1); 8 | } 9 | public async Task MethodWithThrow() 10 | { 11 | await Task.Delay(1); 12 | throw new(); 13 | } 14 | public async Task MethodGeneric() 15 | { 16 | await Task.Delay(1); 17 | return 1; 18 | } 19 | public async Task MethodWithThrowGeneric() 20 | { 21 | await Task.Delay(1); 22 | throw new(); 23 | } 24 | } -------------------------------------------------------------------------------- /AsyncErrorHandler.Fody/AsyncErrorHandler.Fody.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AsyncErrorHandler.Fody/HandleMethodFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Fody; 4 | using Mono.Cecil; 5 | 6 | public class HandleMethodFinder 7 | { 8 | public ModuleDefinition ModuleDefinition; 9 | public MethodReference HandleMethod; 10 | public IAssemblyResolver AssemblyResolver; 11 | 12 | public void Execute() 13 | { 14 | var errorHandler = GetTypeDefinition(); 15 | var handleMethod = errorHandler.Methods.FirstOrDefault(_ => _.Name == "HandleException"); 16 | if (handleMethod == null) 17 | { 18 | throw new WeavingException($"Could not find 'HandleException' method on '{errorHandler.FullName}'."); 19 | } 20 | if (!handleMethod.IsPublic) 21 | { 22 | throw new WeavingException("Method 'AsyncErrorHandler.HandleException' is not public."); 23 | } 24 | if (!handleMethod.IsStatic) 25 | { 26 | throw new WeavingException("Method 'AsyncErrorHandler.HandleException' is not static."); 27 | } 28 | if (handleMethod.Parameters.Count != 1) 29 | { 30 | throw new WeavingException("Method 'AsyncErrorHandler.HandleException' must have only 1 parameter that is of type 'System.Exception'."); 31 | } 32 | var parameterDefinition = handleMethod.Parameters.First(); 33 | var parameterType = parameterDefinition.ParameterType; 34 | if (parameterType.FullName != "System.Exception") 35 | { 36 | throw new WeavingException("Method 'AsyncErrorHandler.HandleException' must have only 1 parameter that is of type 'System.Exception'."); 37 | } 38 | HandleMethod = ModuleDefinition.ImportReference(handleMethod); 39 | } 40 | 41 | TypeDefinition GetTypeDefinition() 42 | { 43 | foreach (var module in GetAllModulesToSearch()) 44 | { 45 | var errorHandler = module.GetTypes().FirstOrDefault(_ => _.Name == "AsyncErrorHandler"); 46 | if (errorHandler != null) 47 | { 48 | return errorHandler; 49 | } 50 | } 51 | var error = 52 | """ 53 | Could not find type 'AsyncErrorHandler'. Expected to find a class with the following signature. 54 | public static class AsyncErrorHandler 55 | { 56 | public static void HandleException(Exception exception) 57 | { 58 | Debug.WriteLine("Exception occurred: " + exception.Message); 59 | } 60 | } 61 | """; 62 | 63 | throw new WeavingException(error); 64 | } 65 | 66 | IEnumerable GetAllModulesToSearch() 67 | { 68 | yield return ModuleDefinition; 69 | 70 | foreach (var reference in ModuleDefinition.AssemblyReferences.Where(x => !IsMicrosoftAssembly(x))) 71 | { 72 | yield return ModuleDefinition.AssemblyResolver.Resolve(reference).MainModule; 73 | } 74 | } 75 | 76 | static bool IsMicrosoftAssembly(AssemblyNameReference reference) 77 | { 78 | return reference.FullName.EndsWith("b77a5c561934e089"); 79 | } 80 | } -------------------------------------------------------------------------------- /AsyncErrorHandler.Fody/MethodProcessor.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | using Mono.Cecil.Rocks; 4 | 5 | public class MethodProcessor 6 | { 7 | public HandleMethodFinder HandleMethodFinder; 8 | 9 | public void Process(MethodDefinition method) 10 | { 11 | method.Body.SimplifyMacros(); 12 | 13 | var instructions = method.Body.Instructions; 14 | for (var index = 0; index < instructions.Count; index++) 15 | { 16 | var line = instructions[index]; 17 | if (line.OpCode != OpCodes.Call) 18 | { 19 | continue; 20 | } 21 | 22 | if (line.Operand is not MethodReference methodReference) 23 | { 24 | continue; 25 | } 26 | 27 | if (!IsSetExceptionMethod(methodReference)) 28 | { 29 | continue; 30 | } 31 | 32 | var previous = instructions[index-1]; 33 | instructions.Insert(index, Instruction.Create(OpCodes.Call, HandleMethodFinder.HandleMethod)); 34 | index++; 35 | if (previous.Operand is not VariableDefinition variableDefinition) 36 | { 37 | throw new($"Expected VariableDefinition but got '{previous.Operand.GetType().Name}'."); 38 | } 39 | instructions.Insert(index, Instruction.Create(previous.OpCode, variableDefinition)); 40 | index++; 41 | 42 | } 43 | method.Body.OptimizeMacros(); 44 | } 45 | 46 | public static bool IsSetExceptionMethod(MethodReference methodReference) 47 | { 48 | return 49 | methodReference.Name == "SetException" && 50 | methodReference.DeclaringType.FullName.StartsWith("System.Runtime.CompilerServices.AsyncTaskMethodBuilder"); 51 | } 52 | } -------------------------------------------------------------------------------- /AsyncErrorHandler.Fody/ModuleWeaver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Fody; 5 | 6 | public class ModuleWeaver : BaseModuleWeaver 7 | { 8 | public override void Execute() 9 | { 10 | var initializeMethodFinder = new HandleMethodFinder 11 | { 12 | ModuleDefinition = ModuleDefinition, 13 | AssemblyResolver = AssemblyResolver 14 | }; 15 | initializeMethodFinder.Execute(); 16 | 17 | var stateMachineFinder = new StateMachineTypesFinder 18 | { 19 | ModuleDefinition = ModuleDefinition, 20 | }; 21 | stateMachineFinder.Execute(); 22 | 23 | var methodProcessor = new MethodProcessor 24 | { 25 | HandleMethodFinder = initializeMethodFinder, 26 | }; 27 | 28 | foreach (var stateMachine in stateMachineFinder.AllTypes) 29 | { 30 | try 31 | { 32 | var moveNext = stateMachine.Methods.First(_ => _.Name == "MoveNext"); 33 | methodProcessor.Process(moveNext); 34 | } 35 | catch (Exception exception) 36 | { 37 | throw new($"Failed to process '{stateMachine.FullName}'.", exception); 38 | } 39 | } 40 | } 41 | 42 | public override IEnumerable GetAssembliesForScanning() 43 | { 44 | yield break; 45 | } 46 | } -------------------------------------------------------------------------------- /AsyncErrorHandler.Fody/StateMachineChecker.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | 4 | public static class StateMachineChecker 5 | { 6 | public static bool IsStateMachine(this TypeDefinition typeDefinition) 7 | { 8 | return typeDefinition.IsIAsyncStateMachine() && 9 | typeDefinition.IsCompilerGenerated(); 10 | } 11 | 12 | public static bool IsCompilerGenerated(this TypeDefinition typeDefinition) 13 | { 14 | return typeDefinition.CustomAttributes.Any(_ => _.Constructor.DeclaringType.Name == "CompilerGeneratedAttribute"); 15 | } 16 | 17 | public static bool IsIAsyncStateMachine(this TypeDefinition typeDefinition) 18 | { 19 | return typeDefinition.Interfaces.Any(_ => _.InterfaceType.Name =="IAsyncStateMachine"); 20 | } 21 | } -------------------------------------------------------------------------------- /AsyncErrorHandler.Fody/StateMachineTypesFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Mono.Cecil; 3 | 4 | public class StateMachineTypesFinder 5 | { 6 | public List AllTypes; 7 | public ModuleDefinition ModuleDefinition; 8 | 9 | public void Execute() 10 | { 11 | AllTypes = new(); 12 | GetTypes(ModuleDefinition.Types); 13 | } 14 | 15 | void GetTypes(IEnumerable typeDefinitions) 16 | { 17 | foreach (var typeDefinition in typeDefinitions) 18 | { 19 | GetTypes(typeDefinition.NestedTypes); 20 | if (typeDefinition.IsStateMachine()) 21 | { 22 | AllTypes.Add(typeDefinition); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /AsyncErrorHandler.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31825.309 5 | MinimumVisualStudioVersion = 16.0.29201.188 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncErrorHandler.Fody", "AsyncErrorHandler.Fody\AsyncErrorHandler.Fody.csproj", "{C3578A7B-09A6-4444-9383-0DEAFA4958BD}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{5A86453B-96FB-4B6E-A283-225BB9F753D3}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyToProcess", "AssemblyToProcess\AssemblyToProcess.csproj", "{7DEC4E2D-F872-434E-A267-0BAD65299950}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyTemplate", "AssemblyTemplate\AssemblyTemplate.csproj", "{CC2EB345-6E0F-4523-9230-C4F6D129D411}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyWithHandlerInReference", "AssemblyWithHandlerInReference\AssemblyWithHandlerInReference.csproj", "{BAB0CB71-BD17-4D16-97AD-751FF53515F4}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncErrorHandler", "AsyncErrorHandler\AsyncErrorHandler.csproj", "{EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}" 17 | ProjectSection(ProjectDependencies) = postProject 18 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD} = {C3578A7B-09A6-4444-9383-0DEAFA4958BD} 19 | EndProjectSection 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2751CBDA-427C-4DE4-8F0C-45173F84365D}" 22 | ProjectSection(SolutionItems) = preProject 23 | Directory.Build.props = Directory.Build.props 24 | EndProjectSection 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Debug|Mixed Platforms = Debug|Mixed Platforms 30 | Debug|x86 = Debug|x86 31 | Release|Any CPU = Release|Any CPU 32 | Release|Mixed Platforms = Release|Mixed Platforms 33 | Release|x86 = Release|x86 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 39 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 40 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|x86.Build.0 = Debug|Any CPU 42 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 45 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU 46 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|x86.ActiveCfg = Release|Any CPU 47 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|x86.Build.0 = Release|Any CPU 48 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 51 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 52 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 56 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU 57 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|x86.ActiveCfg = Release|Any CPU 58 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 61 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 62 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Debug|x86.ActiveCfg = Debug|Any CPU 63 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 66 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Release|Mixed Platforms.Build.0 = Release|Any CPU 67 | {7DEC4E2D-F872-434E-A267-0BAD65299950}.Release|x86.ActiveCfg = Release|Any CPU 68 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 71 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 72 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Debug|x86.ActiveCfg = Debug|Any CPU 73 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Release|Any CPU.Build.0 = Release|Any CPU 75 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 76 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Release|Mixed Platforms.Build.0 = Release|Any CPU 77 | {CC2EB345-6E0F-4523-9230-C4F6D129D411}.Release|x86.ActiveCfg = Release|Any CPU 78 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 81 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 82 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Debug|x86.ActiveCfg = Debug|Any CPU 83 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 84 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Release|Any CPU.Build.0 = Release|Any CPU 85 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 86 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Release|Mixed Platforms.Build.0 = Release|Any CPU 87 | {BAB0CB71-BD17-4D16-97AD-751FF53515F4}.Release|x86.ActiveCfg = Release|Any CPU 88 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 91 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 92 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Debug|x86.Build.0 = Debug|Any CPU 94 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 97 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Release|Mixed Platforms.Build.0 = Release|Any CPU 98 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Release|x86.ActiveCfg = Release|Any CPU 99 | {EF9EC1C1-6D40-4FA1-A03A-AE010D3B731E}.Release|x86.Build.0 = Release|Any CPU 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(ExtensibilityGlobals) = postSolution 105 | SolutionGuid = {9898D83A-8477-4981-9CF4-96E9E6E0456C} 106 | EndGlobalSection 107 | EndGlobal 108 | -------------------------------------------------------------------------------- /AsyncErrorHandler/AsyncErrorHandler.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;netstandard2.0 5 | true 6 | key.snk 7 | Simon Cropp 8 | An extension for Fody to integrate error handling into async and TPL code. 9 | Async, Exception Handling, ILWeaving, Fody, Cecil 10 | $(SolutionDir)nugets 11 | https://raw.githubusercontent.com/Fody/AsyncErrorHandler/master/package_icon.png 12 | https://github.com/Fody/AsyncErrorHandler 13 | MIT 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /AsyncErrorHandler/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fody/AsyncErrorHandler/d80cbdf315f6d74a7ba5d7a6bc66053f80e15dde/AsyncErrorHandler/key.snk -------------------------------------------------------------------------------- /CommonAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("AsyncErrorHandler")] 4 | [assembly: AssemblyProduct("AsyncErrorHandler")] 5 | [assembly: AssemblyVersion("1.1.1")] 6 | [assembly: AssemblyFileVersion("1.1.1")] -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.3.0 5 | true 6 | preview 7 | NU5118 8 | true 9 | all 10 | low 11 | 12 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) Simon Cropp and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Tests/AssemblyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | public static class AssemblyExtensions 5 | { 6 | public static dynamic GetInstance(this Assembly assembly, string className) 7 | { 8 | var type = assembly.GetType(className, true); 9 | //dynamic instance = FormatterServices.GetUninitializedObject(type); 10 | return Activator.CreateInstance(type); 11 | } 12 | } -------------------------------------------------------------------------------- /Tests/InSameAssemblyTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | using Fody; 5 | using Xunit; 6 | 7 | public class InSameAssemblyTests 8 | { 9 | FieldInfo exceptionField; 10 | dynamic target; 11 | 12 | public InSameAssemblyTests() 13 | { 14 | var weaver = new ModuleWeaver(); 15 | 16 | var testResult = weaver.ExecuteTestRun( 17 | "AssemblyToProcess.dll", 18 | assemblyName: "InSameAssembly", 19 | runPeVerify: false); 20 | target = testResult.GetInstance("Target"); 21 | var errorHandler = testResult.Assembly.GetType("AsyncErrorHandler"); 22 | exceptionField = errorHandler.GetField("Exception"); 23 | } 24 | 25 | [Fact] 26 | public async Task Method() 27 | { 28 | ClearException(); 29 | await target.Method(); 30 | Assert.Null(GetException()); 31 | } 32 | 33 | [Fact] 34 | public async Task MethodWithThrow() 35 | { 36 | ClearException(); 37 | try 38 | { 39 | await target.MethodWithThrow(); 40 | } 41 | catch 42 | { 43 | } 44 | 45 | Assert.NotNull(GetException()); 46 | } 47 | 48 | [Fact] 49 | public async Task MethodGeneric() 50 | { 51 | ClearException(); 52 | await target.MethodGeneric(); 53 | Assert.Null(GetException()); 54 | } 55 | 56 | [Fact] 57 | public async Task MethodWithThrowGeneric() 58 | { 59 | ClearException(); 60 | try 61 | { 62 | await target.MethodWithThrowGeneric(); 63 | } 64 | catch 65 | { 66 | } 67 | 68 | Assert.NotNull(GetException()); 69 | } 70 | 71 | void ClearException() 72 | { 73 | exceptionField.SetValue(null, null); 74 | } 75 | 76 | Exception GetException() 77 | { 78 | return (Exception) exceptionField.GetValue(null); 79 | } 80 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/WithHandlerInReferenceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | using Fody; 5 | using Xunit; 6 | 7 | public class WithHandlerInReferenceTests 8 | { 9 | FieldInfo exceptionField; 10 | dynamic target; 11 | 12 | public WithHandlerInReferenceTests() 13 | { 14 | var weaver = new ModuleWeaver(); 15 | 16 | var testResult = weaver.ExecuteTestRun("AssemblyWithHandlerInReference.dll", runPeVerify: false); 17 | target = testResult.GetInstance("Target"); 18 | var errorHandler = Type.GetType("AsyncErrorHandler, AssemblyToProcess"); 19 | exceptionField = errorHandler.GetField("Exception"); 20 | } 21 | 22 | [Fact] 23 | public async Task Method() 24 | { 25 | ClearException(); 26 | await target.Method(); 27 | Assert.Null(GetException()); 28 | } 29 | 30 | [Fact] 31 | public async Task MethodWithThrow() 32 | { 33 | ClearException(); 34 | try 35 | { 36 | await target.MethodWithThrow(); 37 | } 38 | catch 39 | { 40 | } 41 | Assert.NotNull(GetException()); 42 | } 43 | 44 | [Fact] 45 | public async Task MethodGeneric() 46 | { 47 | ClearException(); 48 | await target.MethodGeneric(); 49 | Assert.Null(GetException()); 50 | } 51 | 52 | [Fact] 53 | public async Task MethodWithThrowGeneric() 54 | { 55 | ClearException(); 56 | try 57 | { 58 | await target.MethodWithThrowGeneric(); 59 | } 60 | catch 61 | { 62 | } 63 | Assert.NotNull(GetException()); 64 | } 65 | 66 | void ClearException() 67 | { 68 | exceptionField.SetValue(null, null); 69 | } 70 | 71 | Exception GetException() 72 | { 73 | return (Exception) exceptionField.GetValue(null); 74 | } 75 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | environment: 3 | DOTNET_NOLOGO: true 4 | DOTNET_CLI_TELEMETRY_OPTOUT: true 5 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 6 | build_script: 7 | - pwsh: | 8 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" 9 | ./dotnet-install.ps1 -JSonFile global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet' 10 | - dotnet build --configuration Release 11 | - dotnet test --configuration Release --no-build --no-restore 12 | test: off 13 | artifacts: 14 | - path: nugets\**\*.nupkg -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.101", 4 | "allowPrerelease": true, 5 | "rollForward": "latestFeature" 6 | } 7 | } -------------------------------------------------------------------------------- /key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fody/AsyncErrorHandler/d80cbdf315f6d74a7ba5d7a6bc66053f80e15dde/key.snk -------------------------------------------------------------------------------- /package_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fody/AsyncErrorHandler/d80cbdf315f6d74a7ba5d7a6bc66053f80e15dde/package_icon.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AsyncErrorHandler.Fody 2 | 3 | [![NuGet Status](https://img.shields.io/nuget/v/AsyncErrorHandler.Fody.svg)](https://www.nuget.org/packages/AsyncErrorHandler.Fody/) 4 | 5 | Fody.AsyncErrorHandler is a [Fody](https://github.com/Fody/Home/) extension for weaving exception handling code into applications which use async code. 6 | 7 | **See [Milestones](../../milestones?state=closed) for release notes.** 8 | 9 | 10 | ### This is an add-in for [Fody](https://github.com/Fody/Home/) 11 | 12 | **It is expected that all developers using Fody [become a Patron on OpenCollective](https://opencollective.com/fody/contribute/patron-3059). [See Licensing/Patron FAQ](https://github.com/Fody/Home/blob/master/pages/licensing-patron-faq.md) for more information.** 13 | 14 | 15 | ## Usage 16 | 17 | See also [Fody usage](https://github.com/Fody/Home/blob/master/pages/usage.md). 18 | 19 | 20 | ### NuGet package 21 | 22 | https://nuget.org/packages/AsyncErrorHandler.Fody/ 23 | 24 | ```powershell 25 | PM> Install-Package Fody 26 | PM> Install-Package AsyncErrorHandler.Fody 27 | ``` 28 | 29 | The `Install-Package Fody` is required since NuGet always defaults to the oldest, and most buggy, version of any dependency. 30 | 31 | 32 | ### Add to FodyWeavers.xml 33 | 34 | Add `` to [FodyWeavers.xml](https://github.com/Fody/Home/blob/master/pages/usage.md#add-fodyweaversxml) 35 | 36 | ```xml 37 | 38 | 39 | 40 | ``` 41 | 42 | 43 | ## Why? 44 | 45 | Because writing plumbing code is dumb and repetitive. 46 | 47 | 48 | ## How? 49 | 50 | IL-weaving after the code is compiled, bro. 51 | 52 | For example, imagine you've got this code to serialize an object to the filesystem: 53 | 54 | ```csharp 55 | public class DataStorage 56 | { 57 | public async Task WriteFile(string key, object value) 58 | { 59 | var jsonValue = JsonConvert.SerializeObject(value); 60 | using (var file = await folder.OpenStreamForWriteAsync(key, CreationCollisionOption.ReplaceExisting)) 61 | using (var stream = new StreamWriter(file)) 62 | await stream.WriteAsync(jsonValue); 63 | } 64 | } 65 | ``` 66 | 67 | After the code builds, the weaver could scan your assembly looking for code which behaves a certain way, and rewrite it to include the necessary handling code: 68 | 69 | ```csharp 70 | public class DataStorage 71 | { 72 | public async Task WriteFile(string key, object value) 73 | { 74 | try 75 | { 76 | var jsonValue = JsonConvert.SerializeObject(value); 77 | using (var file = await folder.OpenStreamForWriteAsync(key, CreationCollisionOption.ReplaceExisting)) 78 | using (var stream = new StreamWriter(file)) 79 | await stream.WriteAsync(jsonValue); 80 | } 81 | catch (Exception exception) 82 | { 83 | AsyncErrorHandler.HandleException(exception); 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | And your application could provide its own implementation of the error handling module: 90 | 91 | 92 | ```csharp 93 | public static class AsyncErrorHandler 94 | { 95 | public static void HandleException(Exception exception) 96 | { 97 | Debug.WriteLine(exception); 98 | } 99 | } 100 | ``` 101 | 102 | Which allows you to intercept the exceptions at runtime. 103 | 104 | 105 | ## What it really does 106 | 107 | So the above example is actually a little misleading. It shows "in effect" what is inject. In reality the injected code is a little more complicated. 108 | 109 | 110 | ### What async actually produces 111 | 112 | So given a method like this 113 | 114 | ```csharp 115 | public async Task Method() 116 | { 117 | await Task.Delay(1); 118 | } 119 | ``` 120 | 121 | The compile will produce this 122 | 123 | ```csharp 124 | [AsyncStateMachine(typeof(d__0)), DebuggerStepThrough] 125 | public Task Method() 126 | { 127 | d__0 d__; 128 | d__.<>4__this = this; 129 | d__.<>t__builder = AsyncTaskMethodBuilder.Create(); 130 | d__.<>1__state = -1; 131 | d__.<>t__builder.Start<d__0>(ref d__); 132 | return d__.<>t__builder.Task; 133 | } 134 | ``` 135 | 136 | So "Method" has become a stub that calls into a state machine. 137 | 138 | The state machine will look like this 139 | 140 | ```csharp 141 | [CompilerGenerated] 142 | struct d__0 : IAsyncStateMachine 143 | { 144 | // Fields 145 | public int <>1__state; 146 | public Target <>4__this; 147 | public AsyncTaskMethodBuilder <>t__builder; 148 | private object <>t__stack; 149 | private TaskAwaiter <>u__$awaiter1; 150 | 151 | // Methods 152 | private void MoveNext(); 153 | [DebuggerHidden] 154 | private void SetStateMachine(IAsyncStateMachine param0); 155 | } 156 | ``` 157 | 158 | The method we care about is `MoveNext`. It will look something like this 159 | 160 | ```csharp 161 | void MoveNext() 162 | { 163 | try 164 | { 165 | TaskAwaiter awaiter; 166 | bool flag = true; 167 | switch (this.<>1__state) 168 | { 169 | case -3: 170 | goto Label_009F; 171 | 172 | case 0: 173 | break; 174 | 175 | default: 176 | awaiter = Task.Delay(1).GetAwaiter(); 177 | if (awaiter.IsCompleted) 178 | { 179 | goto Label_006F; 180 | } 181 | this.<>1__state = 0; 182 | this.<>u__$awaiter1 = awaiter; 183 | this.<>t__builder.AwaitUnsafeOnCompletedd__0>(ref awaiter, ref this); 184 | flag = false; 185 | return; 186 | } 187 | awaiter = this.<>u__$awaiter1; 188 | this.<>u__$awaiter1 = new TaskAwaiter(); 189 | this.<>1__state = -1; 190 | Label_006F: 191 | awaiter.GetResult(); 192 | awaiter = new TaskAwaiter(); 193 | } 194 | catch (Exception exception) 195 | { 196 | this.<>1__state = -2; 197 | this.<>t__builder.SetException(exception); 198 | return; 199 | } 200 | Label_009F: 201 | this.<>1__state = -2; 202 | this.<>t__builder.SetResult(); 203 | } 204 | ``` 205 | 206 | Most of that can be ignored. The important thing to note is that it is swallowing exceptions in a catch. And passing that exception to a `SetException` method. 207 | 208 | So when AsyncErrorHandler does its weaving it searches for `SetException(exception);` and then modifies the catch to look like this. 209 | 210 | ```csharp 211 | catch (Exception exception) 212 | { 213 | this.<>1__state = -2; 214 | AsyncErrorHandler.HandleException(exception); 215 | this.<>t__builder.SetException(exception); 216 | return; 217 | } 218 | ``` 219 | 220 | 221 | ## Icon 222 | 223 | Icon courtesy of [The Noun Project](https://thenounproject.com) 224 | --------------------------------------------------------------------------------