2010-08-20 156 views
0

我注意到Windows窗体场景中涉及线程和UI的一些奇怪行为,所以,当然,这意味着要使用InvokeRequired属性。情况:我的应用程序使用一个线程来完成一些工作,线程向UI发送一个事件。用户界面显示基于国际化系统的消息,该系统由带有密钥的字典组成。 I18N系统无法在字典中找到密钥并崩溃。InvokeRequired异常处理

说明:应用程序处于调试模式,我有一个尝试赶上整个“Application.Run();”回到Program.cs中。然而,那个try-catch没有被达到,因为我在这里要讨论的是基于内部异常处理,但我提到它以防万一。

所以现在这里来了有趣的部分:

  1. 为什么,我的生活中,确实Visual Studio的“检查员”异常从我的信息?在下面的代码中,你会看到if(InvokeRequired)分支,一个try-catch。我记录异常。 ex.InnerException是NULL,提供的ex.StackTrace是贫血的(只有1步)。现在,如果我尝试捕获并简单地让它通过调试器崩溃,我会得到一个非常宽泛的栈跟踪。这是为什么?

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

  3. 其他分支(即InvokeRequired ==假),如果我把一个try-catch,我可以成功地捕捉我的异常给国际化系统。正如你所看到的,我试图用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); 
    } 
} 
+1

#2有点混乱。这两者都不意味着这两个例外都会提供关于i18N崩溃的信息。我想你的意思是说,没有任何例外给出这些信息。你可能想重新说一遍。 对不起,如果我是一个语法纳粹:P – 2010-08-20 10:34:46

+0

真:: :: D.我更新,对不起! – Axonn 2010-08-20 10:47:38

+0

有没有什么办法可以让我的问题更好,让更多的人看到它? :: - >。 – Axonn 2010-08-20 13:08:08

回答

1

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

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

如果您还不能升级到.NET 4.0,则有一种解决方法。 Microsoft's Rx library包含一个名为PrepareForRethrow的扩展方法为Exception的CoreEx.dll。这在.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更适合作为业务层的抽象“线程上下文”。

+0

你好斯蒂芬,并感谢你的非常详细的答案:: - D.迁移到.net 4.0将来可能对我来说(更像2011年),所以现在我只是满足,我知道为什么会发生这种行为。这对我来说并不是什么大不了的事情,只是让我感到厌烦。我已经通过在ELSE分支中简单地放置一个logging-Try-catch来缓解InvokeRequired的缺点,在那里我得到了堆栈跟踪和一切。这使得软木塞成为问题,我可以愉快地滑冰到其他任务:: - D. – Axonn 2010-08-20 13:57:32

1

对Windows.Forms对象的调用导致函数在单独的线程上被调用。如果在调用的函数中引发了异常,则会捕获异常并引发新的TargetInvocationException。

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

所以,尽量做到这样:

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

编辑:另外,如果您展开调试InnerException属性,你就可以访问它的堆栈跟踪,即使只为纯文本。

+0

这不起作用。如果我将catch改为那个,那么错误不会再被捕获,并在“try”块内崩溃。看看.Net文档,我发现这个方法用于反射场景。也许你被它的名字误导了? :: - )。 – Axonn 2010-08-20 10:57:54

+0

嗯。奇怪。 Windows.Forms应该通过System.Reflection在内部调用你的方法。你能否在catch-line上放置一个断点并检查Exception是什么类型以及InnerException是否为null? 编辑:请尝试更改捕获语句以捕获(Exception ex),但仍然访问Log() - Statement中的ex.InnerException。 – Emiswelt 2010-08-20 11:03:07

+0

正如我最初所说,异常的InnerException为null。异常类型是“KeyNotFoundException”。访问ex.InnerException不起作用,它是空的:: - ( – Axonn 2010-08-20 11:18:09

1

是的,这是Control.Invoke()的内置行为。它只将最深的嵌套InnerException封送回调用者。不太清楚为什么他们这样做,除了避免编组代码引发的异常,并会使读者感到困惑。这是明确的,你不能改变它的工作方式。

但是你的眼睛在球上,真正的问题是字符串确实无法在字典中找到。原因是你的后台线程运行在与你的UI线程不同的文化中。不同文化有不同的字符串比较规则。你或者需要给你的字典一个不同的比较器(StringComparer.InvariantCulture),或者你应该将你的后台线程切换到与你的UI线程相同的区域。

处理UI线程中的非系统默认文化可能很困难,所有其他线程都将以系统默认值启动。特别是线程池线程麻烦,你并不总是控制他们如何开始。文化不是Thread.ExecutionContext的一部分,所以不会被转发。这可能会导致微妙的错误,就像你遇到的错误一样。其他的缺点就是,比如SortedList,当使用不同文化的线程读取时,SortedList突然变得未排序。强烈建议使用系统默认文化。它的用户可能会使用它。

+0

你好汉斯和谢谢回答:: - )。顺便说一句,如果它没有得到回答,有什么方法可以让问题稍微有点出现呢?我的意思是,合法的方式,而不是垃圾邮件的网站,当然:: - )。关于你的信息:谢谢你让我走出黑暗。我不知道这是“预期的行为”。我也不知道文化的东西,这是有用的信息,即使我的错误没有任何关系:: D(我的国际化系统不是基于任何系统设置,它是用户选择的,它是自定义的由我自己通过XML文件制作)。 – Axonn 2010-08-20 13:54:57

+1

编辑您的问题,使其更清晰。吸引回答者并使其发生颠簸。 CurrentCulture值之间的不匹配太不好解释,不予考虑。它真的*是由系统设置确定的。控制面板+区域。 – 2010-08-20 14:17:24

+0

哦,我从不否认它是由系统设置控制的。当然如此。我只是说我不关心我的应用程序中的那些东西。另外,我没有不考虑你的解释! :: - )。 – Axonn 2010-08-20 15:50:17