【问题标题】:Standard way of handling errors originating from threads处理源自线程的错误的标准方法
【发布时间】:2010-08-20 13:51:50
【问题描述】:

这个问题已经被提过很多次了,但是我想再问一遍,因为我在这里读到了一些对我来说似乎不正确的东西(可能是因为其中一个与 .NET CF 有关) ,并且我要求验证我认为合理的特定方法,并希望您提供任何反馈。

无论如何,情况和其他人一样——我想处理源自线程的错误。目前,我的代码运行良好,因为我从线程中抛出异常,但我使用的是 Invoke,而不是 BeginInvoke。

此时您可能想知道,“他为什么要创建一个线程然后调用 Invoke?为什么不直接调用一个函数?”。问题是我想通过一个参数为调用者提供灵活性,该参数指示操作应该是同步的还是异步的。

作为测试,我在异步操作模式下对其进行了测试,正如预期的那样,它静默失败,应用程序在工作线程抛出异常时在调试器中死掉。

为了确保我对线程中的异常行为以及disprove the statement in this question on SO(至少就 .NET 3.5 而言)有基本的了解,我在 WPF 应用程序中编写了一些测试代码:

代码已删除

因此,在主线程中处理源自另一个线程的事件似乎应该没有问题。

如果您同意我上面的测试,我的问题很简单——编写我的方法是否可以接受,该方法同时提供同步和异步行为,其中所有业务逻辑都包含在 try/catch 块中?内部错误被困在线程内部,如果是同步调用,则抛出异常,如果是异步调用,则引发事件?在我上面的示例代码中,我也在引发事件后抛出了异常。我不确定这是否或为什么会给我带来任何问题。

Pseudo-pseudocode:

TestFunc(async);

private TestFunc(bool async)
{
  try {
    throw new MyAppException("error occurred.");
  } catch(MyAppException ex) {
    async ? RaiseErrorEvent : throw;
  }
}

更新

好的,正如汉斯所说,无论我从线程中抛出异常,它都会导致问题 - 句号。我能够测试这个案例,果然,异常被抛出,如果你按下 F5,它只会被一遍又一遍地抛出,并且永远不会终止线程。我不理解这种特殊行为——它只是通过调试器运行时的工作方式吗?我想这意味着我必须检查该方法是被称为同步还是异步。如果同步,则抛出错误。如果是异步的,则引发事件。

也许更好的方法是强制客户端始终处理事件错误,而不是捕获异常?有人对这种方法有想法吗?

【问题讨论】:

    标签: c# multithreading events exception exception-handling


    【解决方案1】:

    不确定您要使用此代码证明什么。只需为 AppDomain.Current.UnhandledException 编写一个异常处理程序并记录或显示 e.ExceptionObject.ToString() 的值。

    如果您正在考虑实际处理异常,那么您有一个完全不同的鱼锅要炸。您有一些代码在随机位置运行和死亡。你真的不知道它进展了多远,你的程序状态有多少被那个线程改变了。处理异常需要恢复程序状态,撤消线程所做的任何事情。无论您编写什么事件处理程序来处理“被轰炸”事件,都无法猜测需要做什么来恢复状态,它对线程的了解还不够。它只知道它没有工作。恢复状态需要线程自己来完成。

    处理“它被轰炸”事件本身就很困难。客户端代码甚至不能显示消息框,它很容易消失在您的主窗口后面。不允许直接更新 UI。调用需要编组到 UI 线程。最好的方法是让客户端代码决定如何通知它。 FileSystemWatcher.SynchronizingObject 属性是一个很好的模式。

    【讨论】:

    • 也许我不应该发布那个代码,我只是展示了一个人为的例子,它从线程中抛出异常作为一些背景信息。是的,我确实需要处理异常。我现在正在做的是在我的线程中捕获异常,然后引发错误事件抛出异常。这样,我的调用者,无论是同步还是异步,都可以随心所欲地处理错误。如果要同步调用,它将使用 try / catch。如果要异步调用,它将注册/注销事件。我只是在征求对这种方法的意见;它似乎工作。
    • 抛出异常会炸毁程序,客户端代码无法捕捉到它。因为事件是在线程上引发的。
    【解决方案2】:

    如果您正在谈论异步执行任务,最典型的模式是在线程中捕获异常并在最终同步以等待任务完成时重新抛出它。例如,IAsyncResult pattern 有一个 BeginX()、EndX() 调用结构,您可以在其中调用 EndX() 来阻止直到完成。 EndX() 调用应该抛出异步操作中发生的任何异常,以便调用者可以决定如何处理它。

    如果您使用 .NET 4.0,Task Parallel Library 广泛支持在执行异步任务时捕获甚至聚合异常,以便启动任务的客户端可以决定如何处理它们。

    【讨论】:

    • 我读到了 .NET 4.0 的 Task 并且很感兴趣。不幸的是,我要到明年年初才能升级到 4.0。我理解你所说的 BeginX、EndX。我不使用这种方法的原因是我的 GUI 可能是同步/异步方法的使用者。在 GUI 中,出于显而易见的原因,我想将其称为非阻塞。如果我调用该方法,并且该方法在 BeginX 之后调用 EndX,那么它本质上是阻塞的。这就是为什么我只调用 BeginX。在这种情况下我可以使用回调,但我只是在等待事件在线程退出之前引发。
    • @Dave,IAsyncResult 的典型使用模式是有一个回调。在回调中,您应该调用 EndX() 来关闭任务并抛出操作期间发生的任何异常。通常认为在未调用 EndX() 的情况下调用 BeginX() 是一种使用错误,尽管这种做法通常不被遵循。
    • 感谢您的建议。我会试一试,看看效果如何。
    • 谢谢,我现在看到我最初使用线程的方法虽然“有效”,但并不完全正确。提供回调并重新加入主线程并在回调中捕获异常非常有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    相关资源
    最近更新 更多