【问题标题】:Control.EndInvoke resets call stack for exceptionControl.EndInvoke 为异常重置调用堆栈
【发布时间】:2011-02-06 23:41:40
【问题描述】:

我不做很多 Windows GUI 编程,所以对于比我更熟悉 WinForms 的人来说,这可能都是常识。不幸的是,我今天在调试期间遇到了任何资源来解释这个问题。

如果我们在异步委托上调用 EndInvoke。我们将在重新抛出方法的执行过程中抛出任何异常。调用堆栈将反映异常的原始来源。

但是,如果我们在 Windows.Forms.Control 上执行类似操作,则 Control.EndInvoke 的实现会重置调用堆栈。这可以通过简单的测试或通过查看 Reflector 中的代码来观察。 EndInvoke 的相关代码摘录在这里:

if (entry.exception != null)
{
   throw entry.exception;
}

我知道 Control 上的 Begin/EndInvoke 和异步委托是不同的,但我预计 Control.EndInvoke 上的行为会相似。

是否有任何理由控制不执行异步委托所做的任何事情来保留原始调用堆栈?

【问题讨论】:

    标签: .net winforms exception asynchronous


    【解决方案1】:

    我不知道真正的原因,但我可以猜测异步委托类似于 RPC,而控制委托可能基于 Win32 消息发送。不同的技术所以这个特性的影响可能不一样。异步委托将受益于所有远程处理代码,开发人员将为此编写代码以在不同进程或计算机之间传输异常调用堆栈,而控制委托将在同一进程中使用 PostMessage 模拟 RPC。不同的团队,不同的代码。

    【讨论】:

      【解决方案2】:

      我不确定为什么 Control 不这样做(可能只是疏忽),但您可以在 .NET 4.0 中通过将任务安排到 UI 表单来解决它:

          private BackgroundWorker bgw;
          private TaskFactory uiTaskFactory;
      
          private void Form1_Load(object sender, EventArgs e)
          {
              this.uiTaskFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
              this.bgw = new BackgroundWorker();
              this.bgw.DoWork += bgw_DoWork;
              this.bgw.RunWorkerAsync();
          }
      
          void bgw_DoWork(object sender, DoWorkEventArgs e)
          {
              var task = this.uiTaskFactory.StartNew(this.OuterTaskFunction);
              try
              {
                  task.Wait();
              }
              catch (Exception ex)
              {
                  // Note: Full stack trace preserved
                  MessageBox.Show(ex.InnerException.ToString());
              }
          }
      
          void OuterTaskFunction()
          {
              this.InnerTaskFunction();
          }
      
          void InnerTaskFunction()
          {
              throw new InvalidOperationException("Blah.");
          }
      

      【讨论】:

        【解决方案3】:

        还要注意Control.EndInvoke 是框架中为数不多的托管EndInvokes 之一(因此您可以在反射器中查看代码)。他们可能应该有一个非托管的帮助器,在原始堆栈到位的情况下抛出。

        事实上,我认为它是唯一托管的EndInvoke,但还有其他带有IAsyncResult 参数的托管End* 例程。我没有检查所有这些,但似乎我审查过的所有这些都只是抛出异常,或者有效地使用 Stephen Cleary 的解决方案,即转向使用 .NET 4 的GetWaiter.GetResult,它确实有一些托管和非托管的恶作剧可以尝试为异常恢复堆栈。

        【讨论】:

          【解决方案4】:

          我没有 100% 阅读您的信息,所以我不确定这是否有帮助,或者我只是在说显而易见的事情, 但是当一个异常被捕获并且你写

          "抛出 iAmAnCaughtExceptionInstance;"

          调用堆栈不会被保存,你应该写

          “扔;”

          然后调用堆栈被保存

          【讨论】:

          • 我知道,但由于我不是实现 Windows.Forms.Control 的人,所以帮助不大。
          猜你喜欢
          • 1970-01-01
          • 2010-11-28
          • 2013-03-31
          • 1970-01-01
          • 2016-07-07
          • 2012-10-15
          • 2017-07-26
          • 2014-04-23
          • 2020-11-14
          相关资源
          最近更新 更多