在Windows场景中,我注意到了一些涉及线程和UI的奇怪行为,因此,这自然意味着要使用InvokeRequired属性。情况:我的应用程序使用线程来做一些工作,并且线程将一个事件发送到UI中。UI显示基于国际化系统的消息,该系统由带有键的字典组成。I18N系统无法在字典中找到密钥并崩溃。
注意:应用程序处于调试模式,我对整个"Application.Run();“返回到Program.cs进行了尝试捕获。但是,没有达到试图捕获,因为我将在这里讨论的是基于内部异常处理,但我提到它只是以防万一。
现在,有趣的部分来了:
我对这些事情感到困惑,也许有人能帮我弄清楚这里的情况。如果你有很强的灯笼,那就是。
这是函数的代码。
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);
}
}发布于 2010-08-20 13:33:51
调用堆栈问题是Control.Invoke已知的一个问题。你失去了呼叫堆栈。抱歉的。这是因为它是使用throw ex;在UI线程上重新抛出的。
最好的解决方案是用后台Task替换后台线程。注意:此解决方案仅适用于.NET 4.0。Task类正确地封送异常。我编写了关于从任务中报告进度的博客条目,该博客条目中的代码将允许您捕获后台线程中的任何UI更新错误,从而保留原始异常及其调用堆栈。
如果您还不能升级到.NET 4.0,有一个解决办法。微软的Rx库包括一个CoreEx.dll,它为Exception提供了一个扩展方法,称为PrepareForRethrow。这在.NET 3.5 SP1和.NET 4.0 (以及SL 3和SL 4)中得到了支持。您需要用一些丑元素包装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更适合作为业务层的抽象“线程上下文”。
发布于 2010-08-20 13:19:26
是的,这是Control.Invoke()的内置行为。它只将最深的嵌套InnerException封送回调用方。不太清楚他们为什么要这么做,除了避免报告由封送代码引发的异常之外,这会使读者感到困惑。它是显式的,您不能改变它的工作方式。
但是要注意球,真正的问题是在字典里确实找不到字符串。其原因是后台线程运行的区域性与UI线程不同。不同的区域性有不同的字符串比较规则。您需要为字典提供不同的比较器(StringComparer.InvariantCulture),或者将后台线程切换到与UI线程相同的区域性。
在UI线程中处理非系统默认区域性可能很困难,所有其他线程都将在系统默认情况下启动。特别是线程池线程是麻烦的,您并不总是控制它们的启动方式。文化不是Thread.ExecutionContext的一部分,所以不会被转发。这可能会引起一些微妙的问题,就像你遇到的那个。其他的缺点是,比方说,SortedList,当被使用不同文化的线程读取时,它会突然变得无序。强烈建议使用系统默认区域性。这是您的用户可能会使用的无论如何。
发布于 2010-08-20 10:53:42
对Windows.Forms对象的调用将导致在单独的线程上调用函数。如果在被调用的函数中抛出异常,则会捕获异常并引发新的TargetInvocationException。
这个TargetInvocationException在它的InnerException属性中包含初始的超值。
所以,试着这样做:
catch (TargetInvocationException ex) { MSASession.ErrorLogger.Log(ex.InnerException); }编辑:此外,如果您在调试器中展开InnerException属性,您将能够访问它的堆栈跟踪,即使只是作为纯文本。
https://stackoverflow.com/questions/3530011
复制相似问题