简体   繁体   中英

Exception thrown in async handler is not caught or handled

For some reason I could not catch an exception thrown inside anonymous async delegate that subscribed to event.

It does not get caught inside TestTestAsync (I suppose because of invoke wait only fastest one) but why it is not caught in unhandled or unobserved or crash app?

ThrowUnobservedTaskExceptions = true also does not make any sense.

using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp5
{
    class Program
    {
        static string lockStr = Guid.NewGuid().ToString();

        public static void ConsoleWriteLine(string Message, ConsoleColor? color = null)
        {
            lock (lockStr)
            {
                var old = Console.ForegroundColor;

                if (color != null)
                    Console.ForegroundColor = color.Value;

                Console.WriteLine(Message);

                if (color != null)
                    Console.ForegroundColor = old;
            }
        }

        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

            try
            {
                var cls = new TestClass();
                cls.TestAsync += async (s) => await Cls_TestRealAsyncAsync(s);
                cls.TestAsync += Cls_TestRealAsync;

                Task.Run(async () => await cls.TestTestAsync()).Wait();

                Thread.Sleep(5000);
            }
            catch (Exception ex)
            {
                ConsoleWriteLine($"{nameof(Main)}: {ex.Message}");
            }
        }

        private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            ConsoleWriteLine($"{nameof(TaskScheduler_UnobservedTaskException)}: {(e.Exception as Exception).Message}", ConsoleColor.Yellow);
        }

        private static Task Cls_TestRealAsync(object sender)
        {
            try
            {
                Thread.Sleep(100);
                throw new NotImplementedException($"{nameof(Cls_TestRealAsync)}");
            }
            catch (Exception ex)
            {
                ConsoleWriteLine(ex.Message, ConsoleColor.Red);
                throw;
            }
        }

        private static async Task Cls_TestRealAsyncAsync(object sender)
        {
            try
            {
                await Task.Run(() => Thread.Sleep(1000));
                throw new NotImplementedException($"{nameof(Cls_TestRealAsyncAsync)}");
            }
            catch (Exception ex)
            {
                ConsoleWriteLine(ex.Message, ConsoleColor.Red);
                throw;
            }
        }

        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            ConsoleWriteLine($"{nameof(CurrentDomain_UnhandledException)}: {(e.ExceptionObject as Exception).Message}", ConsoleColor.Yellow);
        }
    }

    public class TestClass
    {
        public delegate Task TestHandlerAsync(object sender);
        public event TestHandlerAsync TestAsync;

        private async Task OnTestAsync()
        {
            if (TestAsync != null)
                await TestAsync.Invoke(this);
        }

        public async Task TestTestAsync()
        {
            try
            {
                await OnTestAsync();
            }
            catch (Exception ex)
            {
                Program.ConsoleWriteLine($"{nameof(TestTestAsync)}: {ex.Message}", ConsoleColor.Green);
            }
        }
    }
}

屏幕截图

PS: I made tests on 4.7.1

Asynchronous code is not necessarily concurrent code, but you should be careful anyway.

This:

private async Task OnTestAsync()
{
    if (TestAsync != null)
        await TestAsync.Invoke(this);
}

can get you in trouble because by the time TestAsync.Invoke is invoked, TestAsync can be null.

But the problem that you're trying to solve is that, not the that the fastest one is awaited but that the last one is awaited.

You should revise your API but, if you can't, try this:

public class TestClass
{
    public delegate Task TestHandlerAsync(object sender);
    public event TestHandlerAsync TestAsync;

    private async Task OnTestAsync()
    {
        var testAsync = this.TestAsync;

        if (testAsync == null)
        {
            return;
        }

        await Task.WhenAll(
            from TestHandlerAsync d in testAsync.GetInvocationList()
            select d.Invoke(this));
    }

    public async Task TestTestAsync()
    {
        try
        {
            await OnTestAsync();
        }
        catch (Exception ex)
        {
            Program.ConsoleWriteLine($"{nameof(TestTestAsync)}: {ex.Message}", ConsoleColor.Green);
        }
    }
}

if you only care to show the first exception.

Or:

public class TestClass
{
    public delegate Task TestHandlerAsync(object sender);
    public event TestHandlerAsync TestAsync;

    private async Task<Exception[]> OnTestAsync()
    {
        var testAsync = this.TestAsync;

        if (testAsync == null)
        {
            return new Exception[0];
        }

        return await Task.WhenAll(
            from TestHandlerAsync d in testAsync.GetInvocationList()
            select ExecuteAsync(d));

        async Task<Exception> ExecuteAsync(TestHandlerAsync d)
        {
            try
            {
                await d(this);
                return null;
            }
            catch (Exception ex)
            {
                return ex;
            }
        }
    }

    public async Task TestTestAsync()
    {
        try
        {
            var exceptions = await OnTestAsync();

            foreach (var exception in exceptions)
            {
                if (exception != null)
                {
                    Program.ConsoleWriteLine($"{nameof(TestTestAsync)}: {exception.Message}", ConsoleColor.Green);
                }
            }
        }
        catch (Exception ex)
        {
            Program.ConsoleWriteLine($"{nameof(TestTestAsync)}: {ex.Message}", ConsoleColor.Green);
        }
    }
}

if you care for all.

Found the answer. It not abandoned. It simply still not fired because of life of my test console was too short.

Unhandled exception will be thrown at GC.Collect()

https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/

During GC, it notices that nobody ever checked the result (and therefore never saw the exception) and so bubbles it up as an unobserved exception.

So next code before main method end will solve issue and I see exception

GC.Collect();
Thread.Sleep(5000);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM