Используя асинхронную CTP от Microsoft для .NET, возможно ли перехватить исключение, вызванное асинхронным методом в вызывающем методе?
public async void Foo()
{
var x = await DoSomethingAsync();
/* Handle the result, but sometimes an exception might be thrown.
For example, DoSomethingAsync gets data from the network
and the data is invalid... a ProtocolException might be thrown. */
}
public void DoFoo()
{
try
{
Foo();
}
catch (ProtocolException ex)
{
/* The exception will never be caught.
Instead when in debug mode, VS2010 will warn and continue.
The deployed the app will simply crash. */
}
}
Поэтому я хочу, чтобы исключение из асинхронного кода всплыло в моем вызывающем коде, если это вообще возможно.
Ответы:
Это немного странно читать, но да, исключение будет пузыриться в вызывающем коде - но только если вы
await
илиWait()
вызовFoo
.Обратите внимание, что использование Wait () может привести к блокировке вашего приложения, если .Net решит выполнить ваш метод синхронно.
Это объяснение http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions довольно хорошее - оно описывает шаги, которые компилятор предпринимает для достижения этой магии.
источник
await
позвонить в Foo, когда Foo возвращает void?async void Foo()
,Type void is not awaitable
?Причина, по которой исключение не перехвачено, состоит в том, что метод Foo () имеет возвращаемый тип void, поэтому при вызове await он просто возвращается. Поскольку DoFoo () не ожидает завершения Foo, обработчик исключений не может быть использован.
Это открывает более простое решение, если вы можете изменить сигнатуры метода - измените
Foo()
так, чтобы он возвращал тип,Task
а затемDoFoo()
могawait Foo()
, как в этом коде:источник
Ваш код не делает то, что вы думаете, что он делает. Асинхронные методы возвращаются сразу после того, как метод начинает ожидать асинхронного результата. Полезно использовать трассировку, чтобы исследовать, как на самом деле ведет себя код.
Код ниже делает следующее:
Когда вы наблюдаете следы
Вы заметите, что метод Run завершается в потоке 2820, в то время как завершен только один дочерний поток (2756). Если вы применили метод try / catch к своему методу await, вы можете «перехватить» исключение обычным способом, хотя ваш код выполняется в другом потоке, когда задача вычисления завершена и ваша контикация выполнена.
Метод вычисления отслеживает выброшенное исключение автоматически, потому что я использовал ApiChange.Api.dll из инструмента ApiChange . Tracing and Reflector очень помогает понять, что происходит. Чтобы избавиться от многопоточности, вы можете создать свои собственные версии GetAwaiter BeginAwait и EndAwait и обернуть не задачу, а, например, Lazy и трассировку внутри ваших собственных методов расширения. Тогда вы получите гораздо лучшее понимание того, что делает компилятор и что делает TPL.
Теперь вы видите, что нет никакого способа получить / поймать ваше исключение назад, так как не осталось ни одного стекового фрейма для распространения какого-либо исключения. Ваш код может делать что-то совершенно другое после того, как вы инициировали асинхронные операции. Это может вызвать Thread.Sleep или даже прекратить. Пока остается один поток переднего плана, ваше приложение будет продолжать успешно выполнять асинхронные задачи.
Вы можете обработать исключение внутри асинхронного метода после того, как асинхронная операция завершилась, и перезвоните в поток пользовательского интерфейса. Рекомендуемый способ сделать это с помощью TaskScheduler.FromSynchronizationContext . Это работает, только если у вас есть поток пользовательского интерфейса, и он не очень занят другими вещами.
источник
Исключение можно перехватить в асинхронной функции.
источник
Также важно отметить, что вы потеряете хронологическую трассировку стека исключения, если у вас есть тип возврата void для асинхронного метода. Я бы порекомендовал вернуть задание следующим образом. Собираюсь сделать отладку намного проще.
источник
return
заявления, этот код работает, однако, так какTask
"неявно" возвращается с помощьюasync / await
.Этот блог объясняет вашу проблему аккуратно в Async Best Practices .
Суть в том, что вы не должны использовать void в качестве возврата для асинхронного метода, если только он не является обработчиком асинхронных событий, это плохая практика, поскольку он не позволяет перехватывать исключения ;-).
Рекомендуется изменить тип возвращаемого значения на Task. Кроме того, попробуйте полностью закодировать асинхронный код, сделать каждый вызов асинхронного метода и вызываться из асинхронных методов. За исключением метода Main в консоли, который не может быть асинхронным (до C # 7.1).
Вы столкнетесь с тупиковыми ситуациями с приложениями с графическим интерфейсом и ASP.NET, если проигнорируете эту рекомендацию. Взаимная блокировка возникает из-за того, что эти приложения работают в контексте, который допускает только один поток и не передает его асинхронному потоку. Это означает, что GUI ожидает синхронно возврата, а асинхронный метод ожидает context: deadlock.
Такое поведение не произойдет в консольном приложении, потому что оно работает в контексте с пулом потоков. Асинхронный метод вернется в другой поток, который будет запланирован. Вот почему тестовое консольное приложение будет работать, но те же вызовы будут тупиковыми в других приложениях ...
источник