Исключение, сгенерированное в асинхронном обработчике, не перехватывается и не обрабатывается
По какой-то причине я не смог поймать исключение, созданное внутри анонимного асинхронного делегата, подписавшегося на событие.
Не попасть внутрь TestTestAsync
(Я полагаю, из-за invoke wait только самый быстрый), но почему он не попадает в необработанное или ненаблюдаемое или аварийное приложение?
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
2 ответа
Асинхронный код не обязательно является параллельным кодом, но вы все равно должны быть осторожны.
Это:
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);
}
}
}
если ты заботишься обо всех.
Нашел ответ. Это не заброшенный. Он просто еще не выстрелил, потому что жизнь моей тестовой консоли была слишком короткой.
Необработанное исключение будет брошено в GC.Collect ()
https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/
Во время GC он замечает, что никто никогда не проверял результат (и поэтому никогда не видел исключения), и поэтому рассматривает его как ненаблюдаемое исключение.
Поэтому следующий код до завершения основного метода решит проблему, и я вижу исключение
GC.Collect();
Thread.Sleep(5000);