Исключение из асинхронного вызова не доходит до catch
Обработчик сохраняет заказ и должен доносить любой сбой до вызывающего кода. SaveAsync бросает DbException, когда база отвергает запись, но блок catch ниже никогда не срабатывает, в лог ничего не попадает, а вызывающий видит успешное завершение.
Ограничения: не менять SaveAsync; оставить HandleAsync асинхронным; вызывающий обязан в итоге увидеть сбой.
public async Task HandleAsync(Order order)
{
try
{
_repository.SaveAsync(order);
}
catch (DbException ex)
{
_logger.LogError(ex, "Save failed");
throw;
}
}
Найдите и исправьте ошибку.
У вызова пропущен await, поэтому возвращённая Task просто отбрасывается. async-метод не бросает исключение в кадр вызывающего — он складывает сбой в свою Task и перебрасывает его только при ожидании этой задачи. Исправление: написать await _repository.SaveAsync(order);.
- ✗Вызывать async-метод без
awaitи полагать, что сбои всё равно дойдут - ✗Считать, что
async-метод бросает синхронно вtry/catchвызывающего - ✗Относиться к возвращённой
Taskкак к необязательной и отбрасывать её
- →Что происходит с исключением, лежащим в
Task, которую никто не ожидает? - →Как предупреждение компилятора или анализатор поймали бы эту отброшенную задачу?
Решение
public async Task HandleAsync(Order order)
{
try
{
await _repository.SaveAsync(order); // ✅ задача ожидается — сбой перебрасывается сюда
}
catch (DbException ex)
{
_logger.LogError(ex, "Save failed");
throw;
}
}
Почему исходный код молчал
async-метод не бросает исключение в кадр вызывающего. Компилятор перегоняет его тело в машину состояний: когда внутри SaveAsync возникает DbException, машина ловит его и складывает в возвращаемую Task через SetException. Задача переходит в состояние Faulted, и исключение ждёт своего часа внутри неё.
Вынуть его оттуда можно только одним способом — ожидать задачу. Именно await спрашивает у задачи её результат и, увидев Faulted, перебрасывает сохранённый сбой (с сохранением исходного стека через ExceptionDispatchInfo).
В исходном коде строка _repository.SaveAsync(order); просто отбрасывает возвращённую Task. К моменту, когда база отвергнет запись, выполнение уже давно покинуло блок try, и catch физически не может ничего перехватить: в этом кадре ничего не бросалось. Метод возвращает успешно завершённую задачу, и вызывающий делает вывод, что заказ сохранён.
⚠️ Задача, чьё исключение никто не наблюдает, начиная с .NET 4.5 больше не роняет процесс — сбой тихо теряется. Именно поэтому отброшенный Task опаснее упавшего.
Компилятор предупреждает об этом (CS4014), а анализаторы вроде CA2012/VSTHRD110 помечают отброшенные задачи как ошибку — не отключайте эти правила.