【问题标题】:Exception thrown in async handler is not caught or handled未捕获或处理异步处理程序中引发的异常
【发布时间】:2018-06-02 16:20:40
【问题描述】:

由于某种原因,我无法捕获订阅事件的匿名异步委托中引发的异常。

它没有被TestTestAsync 捕获(我想是因为调用等待只有最快的一个)但是为什么它没有被捕获在未处理或未观察到或崩溃的应用程序中?

ThrowUnobservedTaskExceptions = true 也没有任何意义。

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:我在 4.7.1 上做了测试

【问题讨论】:

  • 澄清:我想知道为什么会遗漏异常,而不是需要更改代码来捕获它。因为现在看起来像一个严重的错误:没有例外,没有应用程序失败。

标签: c# asynchronous exception async-await unhandled-exception


【解决方案1】:

异步代码不一定是并发代码,但还是要小心。

这个:

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

可能会给您带来麻烦,因为在调用 TestAsync.Invoke 时,TestAsync 可能为空。

但是您要解决的问题是,不是等待最快的,而是等待最后一个。

你应该修改你的 API,但如果你不能,试试这个:

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);
        }
    }
}

如果您只想显示第一个异常。

或者:

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);
        }
    }
}

如果你关心所有人。

【讨论】:

  • 感谢您的回答。但我想知道它为什么会发生而不是如何重写代码(我知道 GetInvocationList)。我对我的问题添加了评论。
  • 这就是多播委托的工作方式!这就是为什么您不会经常看到非 void 返回代表的组成。
  • 我的意思是“为什么我会丢失未处理的异常并且应用程序不会崩溃”。但我找到了答案 - 它实际上并没有松动并且应用程序正在崩溃:)
【解决方案2】:

找到了答案。它没有被抛弃。由于我的测试控制台的寿命太短,它仍然没有被触发。

GC.Collect() 会抛出未处理的异常

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

在 GC 期间,它注意到没有人检查过结果(并且 因此从未见过异常),因此将其冒泡为 未观察到的异常。

所以主方法结束之前的下一个代码将解决问题,我看到异常

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

【讨论】:

    猜你喜欢
    • 2014-03-19
    • 1970-01-01
    • 1970-01-01
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-02
    • 2017-10-30
    相关资源
    最近更新 更多