InvokeRequiredexception处理

我注意到在Windows窗体方案中有一些涉及线程和UI的奇怪行为,因此,这自然意味着使用了InvokeRequired属性。 情况:我的应用程序使用一个线程来做一些工作,并且线程将一个事件发送到UI。 UI显示基于国际化系统的消息,该系统由具有键的字典组成。 I18N系统无法在字典和崩溃中找到密钥。

注意:应用程序处于调试模式,我在整个“Application.Run();”上有一个try-catch。 回到Program.cs。 但是,没有达到try-catch,因为我将在这里讨论的是基于内部exception处理,但我提到它以防万一。

所以现在有趣的部分:

  1. 为什么,对于我的生活,Visual Studio“审查”我的exception信息? 在下面的代码中,您将在if(InvokeRequired)分支上看到try-catch。 我记录了exception。 ex.InnerException为NULL,提供的ex.StackTrace是贫血的(只有一步)。 现在,如果我评论 try-catch并让它通过Debugger崩溃,我会得到一个更加简单的堆栈跟踪。 这是为什么?

  2. 更糟糕的 ,两个堆栈跟踪版本都没有包含有关i18N崩溃的任何信息。 他们只是说“给定的密钥不在字典中”。 并给我一个堆栈跟踪到Invoke声明。

  3. else分支(即InvokeRequired == false )上,如果我放了一个try-catch,我可以成功地将我的exception捕获回i18n系统。 如您所见,我尝试将我的exception与InnerException一起发送回InvokeRequired == true分支。 但是,即便如此,InnerException仍然在那里保持NULL,我无法访问我的i18N错误。

我对所有这些事情感到困惑,也许有人可以帮助我们解决这些问题。 如果你有非常强大的灯笼。

这是函数的代码。

private delegate void AddMessageToConsole_DELEGATE (frmMainPresenter.PresenterMessages message); private void AddMessageToConsole (frmMainPresenter.PresenterMessages message) { if (InvokeRequired) { //Catching any errors that occur inside the invoked function. try { Invoke(new AddMessageToConsole_DELEGATE(AddMessageToConsole), message); } catch (Exception ex) { MSASession.ErrorLogger.Log(ex); } //Invoke(new AddMessageToConsole_DELEGATE(AddMessageToConsole), message); } else { string message_text = ""; //Message that will be displayed in the Console / written in the Log. try { message_text = I18N.GetTranslatedText(message) } catch (Exception ex) { throw new Exception(ex.Message, ex); } txtConsole.AppendText(message_text); } } 

调用堆栈问题是Control.Invoke的已知问题。 你丢失了调用堆栈。 抱歉。 这是因为它使用throw ex;在UI线程上重新throw ex;

最好的解决方案是用后台Task替换后台线程。 注意:此解决方案仅适用于.NET 4.0。 Task类正确地编组exception。 我写了一篇关于报告任务进度的博客文章 ,该博客条目中的代码将允许您捕获后台线程中的任何UI更新错误,保留原始exception及其调用堆栈。

如果您还不能升级到.NET 4.0,则有一种解决方法。 Microsoft的Rx库包含一个CoreEx.dll,它有一个名为PrepareForRethrow Exception扩展方法。 .NET 3.5 SP1和.NET 4.0(以及SL 3和SL 4)支持此function。 你需要用一些更丑陋的东西包装你的UI更新程序方法:

 private delegate void AddMessageToConsole_DELEGATE (frmMainPresenter.PresenterMessages message); private void AddMessageToConsole (frmMainPresenter.PresenterMessages message) { if (InvokeRequired) { // Invoke the target method, capturing the exception. Exception ex = null; Invoke((MethodInvoker)() => { try { AddMessageToConsole(message); } catch (Exception error) { ex = error; } }); // Handle error if it was thrown if (ex != null) { MSASession.ErrorLogger.Log(ex); // Rethrow, preserving exception stack throw ex.PrepareForRethrow(); } } else { string message_text = ""; //Message that will be displayed in the Console / written in the Log. try { message_text = I18N.GetTranslatedText(message) } catch (Exception ex) { throw new Exception(ex.Message, ex); } txtConsole.AppendText(message_text); } } 

注意:我建议您从ISynchronizeInvoke开始迁移。 它是一个过时的接口,不会被转移到更新的UI框架(例如,WPF,Silverlight)。 替换是SynchronizationContext ,它支持WinForms,WPF,Silverlight,ASP.NET等SynchronizationContext更适合作为业务层的抽象“线程上下文”。

在Windows.Forms对象上调用会导致在单独的线程上调用该函数。 如果在您调用的函数中抛出exception,则会捕获exception并抛出新的TargetInvocationException。

此TargetInvocationException包含其InnerException属性中的初始Excpetion。

所以,尝试这样做:

 catch (TargetInvocationException ex) { MSASession.ErrorLogger.Log(ex.InnerException); } 

编辑:此外,如果在调试器中展开InnerException属性,您将能够访问它的堆栈跟踪,即使只是纯文本。

是的,这是Control.Invoke()的内置行为。 它只将最深层的嵌套InnerException封送回调用者。 不太清楚为什么他们这样做,除了避免报告由编组代码引起的exception并且会使读者混淆。 这是明确的,你不能改变它的工作方式。

但要注意球,真正的问题是字符串确实无法在字典中找到。 原因是您的后台线程与UI线程的文化不同。 不同的文化有不同的字符串比较规则 您需要为字典提供不同的比较器(StringComparer.InvariantCulture),或者您应该将后台线程切换到与UI线程相同的文化。

在UI线程中处理非系统默认文化可能很困难,所有其他线程将使用系统默认启动。 特别是线程池线程很麻烦,你并不总是控制它们如何开始。 而文化不是Thread.ExecutionContext的一部分,因此不会被转发。 这可能会导致细微的错误,例如您遇到的错误。 其他的肮脏是,例如,SortedList,当使用不同文化的线程读取时,它突然变得不完整。 强烈建议使用系统默认文化。 无论如何,它是您的用户可能使用的。