【问题标题】:InvokeRequired Exception HandlingInvokeRequired 异常处理
【发布时间】:2010-08-20 21:33:51
【问题描述】:

我注意到在涉及线程和 UI 的 Windows 窗体场景中出现了一些奇怪的行为,因此,这自然意味着要使用 InvokeRequired 属性。情况:我的应用程序使用线程做一些工作,线程将事件发送到 UI。 UI 显示基于国际化系统的消息,该系统由带有键的字典组成。 I18N 系统在字典中找不到键而崩溃。

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

现在有趣的部分来了:

  1. 为什么 Visual Studio 会“审查”我的异常信息?在下面的代码中,您将在 if (InvokeRequired) 分支上看到一个 try-catch。我记录了异常。 ex.InnerException 是 NULL 并且提供的 ex.StackTrace 是贫血的(其中只有 1 步)。现在,如果我注释 try-catch 并简单地让它通过调试器崩溃,我会得到一个更丰富的堆栈跟踪。这是为什么?

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

  3. else 分支上(即 InvokeRequired == false),如果我放了一个 try-catch,我可以成功地将我的异常捕获回i18n 系统。如您所见,我尝试将带有 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);
  }
}

【问题讨论】:

  • #2 有点令人困惑。既不和不意味着两个例外都提供有关 i18N 崩溃的信息。我认为您的意思是说这两个例外都没有提供该信息。你可能想改写它。对不起,如果我是一个语法纳粹:P
  • True ::- D. 我更新了,抱歉!
  • 有什么办法可以让更多人看到我的问题吗? ::- >.

标签: c#


【解决方案1】:

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

但是请注意,真正的问题是在字典中确实找不到该字符串。原因是您的后台线程与您的 UI 线程以不同的文化运行。不同的文化有不同的字符串比较规则。你要么需要给你的字典一个不同的比较器(StringComparer.InvariantCulture),要么你应该将你的后台线程切换到与你的 UI 线程相同的文化。

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

【讨论】:

  • 汉斯您好,感谢您回答 ::- )。顺便说一句,如果没有回答,有没有办法以某种方式提出一个问题?我的意思是,一种合法的方式,当然不会向网站发送垃圾邮件 ::- )。关于你的信息:谢谢你让我走出黑暗。我不知道这是“预期的行为”。我也不知道文化的东西,这是有用的信息,即使我的错误与那个 ::- D 没有任何关系(我的国际化系统不基于任何系统设置,它是用户选择的并且它是自定义的我自己通过 XML 文件制作的)。
  • 编辑您的问题以使其更清晰。吸引回答者并将其提高。 CurrentCulture 价值观之间的不匹配是一个太大的解释,不能不考虑。它真的由系统设置决定的。控制面板 + 区域。
  • 哦,我从不否认它是由系统设置控制的。当然是。我只是说我不关心我的应用程序中的那些东西。另外,我没有不考虑你的解释! ::- ).
【解决方案2】:

调用堆栈问题是Control.Invoke 的一个已知问题。你失去了调用堆栈。对不起。这是因为它在 UI 线程上使用 throw ex; 重新抛出。

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

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

【讨论】:

  • 您好 Stephen,感谢您非常详细的回答 ::- D. 将来(更像是 2011 年)迁移到 .Net 4.0 对我来说是可能的,所以现在我会很满意我知道为什么会发生这种行为。这对我来说没什么大不了的:这只是让我恼火的事情。我已经通过在 ELSE 分支中简单地放置一个 logging-Try-catch 来减轻 InvokeRequired 的缺点,在那里我得到了堆栈跟踪和所有内容。这给问题带来了麻烦,我可以愉快地滑到其他任务中::- D.
【解决方案3】:

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

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

所以,试着这样做:

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

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

【讨论】:

  • 那行不通。如果我将捕获更改为该错误,则不会再捕获错误并在“尝试”块中崩溃。查看 .Net 文档,我发现此方法用于反射场景。也许你被它的名字误导了? ::- ).
  • 嗯。奇怪的。 Windows.Forms 应该通过 System.Reflection 在内部调用您的方法。您能否在捕获线上放置一个断点并检查异常是什么类型以及 InnerException 是否为空?编辑:请尝试将 catch 语句改回 catch(Exception ex),但仍在 Log()-Statement 中访问 ex.InnerException。
  • InnerException 的异常是空的,正如我最初所说的那样。异常类型是“KeyNotFoundException”。访问 ex.InnerException 不起作用,它是 null ::- (
  • 这真的很奇怪。您是否已经尝试过使用调试器单步执行并隔离引发异常的确切步骤?为此,将 Invoke 语句分成几个两个单独的语句会很有用:创建委托和调用委托。您还确定 i18N 调用会导致异常吗?您是否也可以尝试删除第二个 try/catch(当 InvokeRequired 为 false 时),然后检查是否存在 InnerException 并检查异常的类型?
  • 委托的创建不会崩溃。 I18N 系统崩溃。是的,我一步一步地找到了错误的确切位置。但我希望使用完整的堆栈跟踪正确记录错误,但事实并非如此。此外,最初,ELSE 分支中没有任何问题。我添加它只是因为我很好奇那里到底发生了什么::- D. 无论有没有抓住 Else 分支(当 InvokeRequired 为假时),事情都保持不变。同类型异常,没有InnerException。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多