dotnet / dotNext

Next generation API for .NET
https://dotnet.github.io/dotNext/
MIT License
1.56k stars 119 forks source link

Metaprogramming: Try-catch not catching exception #223

Closed GerardSmit closed 4 months ago

GerardSmit commented 4 months ago

Catching an exception with Try().Catch() in AsyncLambda doesn't work as expected.
When the task is not finished synchronized, the exception is not caught.

Example

using System.Linq.Expressions;
using DotNext.Linq.Expressions;
using DotNext.Metaprogramming;
using static DotNext.Metaprogramming.CodeGenerator;

var lambda = AsyncLambda(Type.EmptyTypes, typeof(int), AsyncLambdaFlags.None, _ =>
{
    Try(() =>
        {
            var methodInfo = typeof(Methods).GetMethod(nameof(Methods.Throw))!;
            var methodResult = Expression.Call(null, methodInfo);

            Return(methodResult.Await());
        })
        .Catch(typeof(Exception), _ =>
        {
            CallStatic(typeof(Console), nameof(Console.WriteLine), Expression.Constant("Exception caught"));
        })
        .End();
});

var action = (Func<Task<int>>)lambda.Compile();

await action();

public static class Methods
{
    public static async Task<int> Throw()
    {
        await Task.Yield();
        throw new InvalidOperationException("Exception was not caught");
    }
}

Expected result

Exception caught

Actual result

Unhandled exception. System.InvalidOperationException: Exception was not caught
   at Methods.Throw() in C:\Sources\ConsoleApp2\ConsoleApp2\Program.cs:line 31
   at lambda_method2(Closure, PoolingAsyncStateMachine`2&)
   at DotNext.Runtime.CompilerServices.PoolingAsyncStateMachine`2.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext() in /_/src/DotNext.Metaprogramming/Runtime/CompilerServices/AsyncStateMachine.Pooling.cs:line 363
--- End of stack trace from previous location ---
   at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
   at System.Threading.Tasks.ValueTask`1.ValueTaskSourceAsTask.<>c.<.cctor>b__4_0(Object state)
--- End of stack trace from previous location ---
   at Program.<Main>$(String[] args) in C:\Sources\ConsoleApp2\ConsoleApp2\Program.cs:line 24
   at Program.<Main>(String[] args)

Notes

  1. If you change the method Throw to the following:

    public static Task<int> Throw()
    {
       throw new InvalidOperationException("Exception was not caught");
    }

    Then the expected result is shown (Exception caught).

  2. If you await the result first with Await(...):

    Try(() =>
        {
            var methodInfo = typeof(Methods).GetMethod(nameof(Methods.Throw))!;
            var methodResult = DeclareVariable("result", Expression.Call(null, methodInfo));
    
            Await(methodResult); // <--- Awaiting the result
    
            Return(methodResult.Await()); // <--- Return the result
        })
        .Catch(typeof(Exception), _ =>
        {
            CallStatic(typeof(Console), nameof(Console.WriteLine), Expression.Constant("Exception caught"));
        })
        .End();

    Then the expected result is shown (Exception caught). I'm using this currently as workaround.

sakno commented 4 months ago

Do you need backport to .NEXT 4.x?

GerardSmit commented 4 months ago

I'm calling the Await(...) as work-around. While it would be nice to have it fixed in 4.x, I can leave the Await(...) in there until the next version.