【问题标题】:Why is Application_UnhandledException not triggered for an exception within an Async Function?为什么异步函数中的异常不会触发 Application_UnhandledException?
【发布时间】:2013-12-19 07:35:02
【问题描述】:

我有一个通过 Caliburn.Micro 约定调用并引发异常的异步成员。当此成员是“Async Sub”时,异常在 Application_UnhandledException 方法中处理。当我将其更改为“异步函数”(仅返回一个任务)时,Application_UnhandledException 方法不再处理该异常。应用程序没有崩溃,异常似乎被吞没了。

通过 Caliburn.Micro 约定绑定按钮 - 点击事件绑定到 DataContext 上同名的方法。

<Button x:Name="MyMember" Content="My Button" />

异步子变体 - 引发 Application_UnhandledException

Public Async Sub MyMember()

异步函数变体 - 异常被吞下

Public Async Function MyMember() As System.Threading.Tasks.Task

documentation for Application_UnhandledException 声明它将被提升

...对于应用程序代码未处理的每个抛出的异常。

要么情况并非如此,要么调用堆栈中的某些应用程序代码更进一步处理异常 - 可能在 Caliburn.Micro 中。

this example 中似乎有类似的东西——尽管在 C# 中。

谁能解释这种行为?像这样吞下异常是个大问题。

更新: 我无法应用此信息,但它可能是相关的。 You can use Coroutine.Completed event to get your exception.

更新 2: Valeriu Caraulean 通过 Caliburn.Micro here 解决了是否使用 async void 或 async Task 的问题。他引用了 Stephen 的文章 Best Practices in Asynchronous Programming,他在其中提供了以下指导。

...你应该更喜欢 async Task 而不是 async void。异步任务方法 使错误处理、可组合性和可测试性更容易。这 本指南的例外是异步事件处理程序,它必须 返回无效。此异常包括逻辑事件的方法 处理程序,即使它们不是字面上的事件处理程序(例如, ICommand.Execute 实现)。

更新 3: 我在 Caliburn.Micro 论坛上发布了related question

【问题讨论】:

  • 请在出现这种情况的地方发布代码。您使用的是最新版本的 Caliburn.Micro 吗? v1.5 添加了对任务和async/await 的支持。行为是否发生在动作方法中?
  • 我已经添加了相关代码例外。不过,它只不过是方法签名而已。
  • @PanagiotisKanavos 我正在使用应该支持 axync/await 的 Caliburn.Micro 1.5.1.0。是的,上面示例中的 MyMember 成员是一个操作方法。

标签: vb.net silverlight async-await caliburn.micro


【解决方案1】:

当您开始使用Async 时,“调用堆栈”就失去了很多意义。以下是实际发生的情况:

  • Async Function 方法返回 TaskTask&lt;T&gt;,并且从该方法抛出的任何异常都将放在返回的任务上。
  • Async Sub 方法没有用于放置异常的 Task,因此从该方法抛出的任何异常都会在 SynchronizationContext 上重新引发,该 SynchronizationContext 是在 Async Sub 方法开始时的当前状态。

所以,这意味着 Async Function 异常被忽略,除非您观察到它们(例如,Await 任务或读取其 Exception 属性)。当任务被垃圾收集时,未观察到的异常被传递给TaskScheduler.UnobservedTaskException,但是这个事件是在请求或应用程序上下文之外引发的(所以你没有太多可以做的响应)。

另一方面,Async Sub 异常会在代表请求上下文的 SynchronizationContext 上重新引发,这就是它们被传递给 Application.UnhandledException 的原因。

理想情况下,Caliburn.Micro 应该了解 Task 处理程序。如果他们不这样做,那么您需要联系该团队以获得最佳解决方案。仅使用Async Sub 的问题在于,当调用代码认为它已经完成时,它会在稍后继续执行;这可能会也可能不会导致问题 - 这完全取决于 Caliburn.Micro。使用Async Function 至少您是在通知调用代码有一个操作正在进行中(通过返回Task),但是否等待该任务(以及是否观察到它的异常)再次完全取决于Caliburn.Micro。

【讨论】:

  • 谢谢斯蒂芬。所以 Caliburn.Micro 很可能是在使用反射之类的机制,根据它的约定来定位一个方法,然后执行它。它似乎没有区分不可等待和等待的成员(那些返回任务的成员)。如果它认识到它正在执行的方法是可等待的并且实际上在等待它,那么我不会有任何问题。这听起来对你吗?除了在使用 Async Subs 和 Caliburn.Micro 方面受到纪律处分之外,我是否有任何不涉及更改 Caliburn.Micro 代码库的选项?
  • 这听起来是正确的。最有可能的是,Caliburn.Micro 只是忽略返回值,而不是将 Task 视为异步操作。我肯定会联系 Caliburn 团队了解您如何使用它;在这一点上,许多框架正在添加对async 的支持和特定用例的帮助。 可以阻塞async 代码或使用我的AsyncEx 库中的AsyncContext 运行一种嵌套循环。但这两种方法都是 hack,您将失去 async 的所有好处,并且并非在所有情况下都有效。
  • 谢谢斯蒂芬。凭借您提供的洞察力,我能够执行更有针对性的搜索,并且巧合地结束了您在异步编程中的最佳实践文章。 msdn.microsoft.com/en-us/magazine/jj991977.aspx
【解决方案2】:

如果在任务中抛出异常,则该任务将出错。您可以通过获取Task.IsFaulted 属性来确定这一点。如果此属性为真,Task.Exception 属性将包含一个 AggregateException,它结合了任务未处理的异常。

在任务中处理异常的最简单方法是等待任务。未处理的异常将通过 async 和 await 的魔力重新出现在等待任务的线程上。

如果您的任务是“即发即弃”任务,则无法处理任何未处理的异常,因为您无法检查或等待该任务。在这种情况下,您应该通过捕获任务中的异常来确保您的任务没有任何未处理的异常。

您还可以处理TaskScheduler.UnobservedTaskException 事件。这有点类似于Application_UnhandledException 事件,只是它是针对任务的。

【讨论】:

  • 这听起来正是我所希望的。不幸的是,部分信任应用程序不支持它。 msdn.microsoft.com/en-us/library/… 当我尝试连接事件时,在运行时出现以下异常。尝试通过安全透明方法“Midwinter.ReasonableBasis.Client.App..ctor()”访问安全关键方法“System.Threading.Tasks.TaskScheduler.add_UnobservedTaskException(System.EventHandler`1) '失败。
【解决方案3】:

除非您等待异常,否则任务会吞下异常(例如,C# await 关键字)。如果我没记错的话,可以在事后检索 Task 抛出的任何异常。这就是为什么当没有等待任务时,Visual Studio(至少在 C# 中)经常发出警告的原因。

【讨论】:

  • 这听起来像是调用我的方法的代码,而不是在等待它。对于 Async Sub 这是不可能的,所以我想这里的异常被视为未处理并触发 Application_UnhandledException 方法。对于异步函数,假设调用者应该等待异常被吞下,如果不是这种情况。你能提出一些防止异常被吞下的方法吗?
  • 您可以存储Task并稍后引用它以查看是否引发了任何异常。
猜你喜欢
  • 2021-01-28
  • 2016-06-20
  • 1970-01-01
  • 2019-08-06
  • 2014-08-17
  • 1970-01-01
  • 1970-01-01
  • 2020-07-09
  • 1970-01-01
相关资源
最近更新 更多