【问题标题】:Exception in bound Property's Set method not caught by Application.ThreadException eventApplication.ThreadException 事件未捕获绑定属性的 Set 方法中的异常
【发布时间】:2014-09-02 08:24:51
【问题描述】:

似乎在属性的 Set 方法中发生的异常不会冒泡到应用程序的 ThreadException 事件。

我们使用该事件和 AppDomain.CurrentDomain.UnhandledException 事件来捕捉应用程序中发生的任何意外事故。异常详细信息会写入日志,以便我们的支持和开发团队可以更好地评估问题。遗憾的是,在这种特殊情况下,Catch All 似乎不够。

StackOverflow 上有几个类似的问题,但没有解决全局异常处理没有捕获异常的问题。我已经知道我们可以修复它,所以不会发生异常。我们可以为每个 setter 添加一个 TryCatch 块。我们可以将 BindingComplete 事件添加到每个数据绑定并以这种方式获取异常。但这一切都违背了在任何其他情况下都可以完美工作的全局异常处理的目的。

要重现此问题,只需创建一个带有文本框的表单,将文本框绑定到属性并在属性的 set 方法中引发异常。将 ThreadException 和 UnhandledException 事件添加到 program.cs。运行程序并在文本框中键入以触发异常。调试器将在异常上中断,按继续 (F5) 让异常冒泡,就像在调试器之外一样。任何正常的异常都会在这些事件中结束,但这次不会。

Form1.cs

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        textBox1.DataBindings.Add("Text", this, "TestValue", true, DataSourceUpdateMode.OnPropertyChanged);            
    }

    private string _TestValue = "";
    public string TestValue
    {
        get{return _TestValue;}
        set
        {
            _TestValue = value;
            throw new Exception("Something bad happened in here");
        }
    }

Program.cs

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        Application.ThreadException += ThreadExceptionHandler;
        AppDomain.CurrentDomain.UnhandledException += new System.UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

        Application.Run(new Form1());
    }

    private static void ThreadExceptionHandler(object sender, System.Threading.ThreadExceptionEventArgs args)
    {
        try
        {
            //RR.Common.ErrorLogRt.WriteError(args.Exception.StackTrace.ToString(), args.Exception.Message.ToString(), true);
            MessageBox.Show(args.Exception.Message);
        }
        catch
        {
            MessageBox.Show("Error writing to exception log. This program will now terminate abnormally.");
            Application.Exit();
        }
    }

static void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
    {
        try
        {
            if (e != null)
            {
                Exception ex = e.ExceptionObject as Exception;
                //RR.Common.ErrorLogRt.WriteError(ex.StackTrace.ToString(), ex.Message.ToString(), true);
                MessageBox.Show(ex.Message);
            }
            else
            {
                MessageBox.Show("Unhandled Error: " + e.ToString());
            }
        }
        catch
        {
            MessageBox.Show("Error writing to exception log. This program will now terminate abnormally.");
            Application.Exit();
        }
    }


 static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
    {
        try
        {
            if (e != null && e.Exception != null && e.Exception.InnerException != null)
            {
                //The unobserved exception is always the same, The actual exception that cause it will be the inner exception.
                Exception ex = e.Exception.InnerException;
                MessageBox.Show(e.Exception.Message);
                //RR.Common.ErrorLogRt.WriteError(ex.StackTrace.ToString(), ex.Message.ToString(), true);
            }
            else
            {
                MessageBox.Show("Unhandled Error: " + e.ToString());
            }


        }
        catch
        {
            MessageBox.Show("Error writing to exception log. This program will now terminate abnormally.");
            Application.Exit();
        }
    }



}

【问题讨论】:

    标签: c# .net data-binding exception-handling


    【解决方案1】:

    Remarks from Binding.FormattingEnabled Property

    将此属性设置为 true 还可以启用错误处理行为和 导致引发 BindingComplete 事件。这个处理程序 事件可以根据成功、错误或 绑定过程中的异常,通过检查 BindingCompleteEventArgs 的 BindingCompleteState 属性 参数。

    The code involved

    internal bool PushData(bool force)
    {
        Exception ex = null;
        if (!force && this.ControlUpdateMode == ControlUpdateMode.Never)
        {
            return false;
        }
        if (this.inPushOrPull && this.formattingEnabled)
        {
            return false;
        }
        this.inPushOrPull = true;
        try
        {
            if (this.IsBinding)
            {
                object value = this.bindToObject.GetValue();
                object propValue = this.FormatObject(value);
                this.SetPropValue(propValue);
                this.modified = false;
            }
            else
            {
                this.SetPropValue(null);
            }
        }
        catch (Exception ex2)
        {
            ex = ex2;
            if (!this.FormattingEnabled)
            {
                throw;
            }
        }
        finally
        {
            this.inPushOrPull = false;
        }
        if (this.FormattingEnabled)
        {
            BindingCompleteEventArgs bindingCompleteEventArgs = this.CreateBindingCompleteEventArgs(BindingCompleteContext.ControlUpdate, ex);
            this.OnBindingComplete(bindingCompleteEventArgs);
            return bindingCompleteEventArgs.Cancel;
        }
        return false;
    }
    

    如您所见,将第 4 个参数传递为 true:DataBindings.Add("Text", this, "TestValue", true 负责捕获 PushData 内部的异常并将其传递给 BindingComplete 事件。如果启用了格式化,除了BindingComplete 之外,没有其他方法(AppDomain.CurrentDomain.FirstChanceException 除外)可以在其他任何地方找到异常。

    【讨论】:

      【解决方案2】:

      我知道 WPF 存在解决方案,但我无法使其适用于 winforms。 似乎异常被框架以某种方式捕获,我找不到要收听的正确跟踪。

      不过,您可以做的是处理第一次机会异常(请注意,这可能会让您捕捉到比您想要的更多的东西)。 这将在您的示例中显示一个带有“这里发生了一些糟糕的事情”的消息框:

          AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;
      //...
      private static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs firstChanceExceptionEventArgs)
      {
          if(firstChanceExceptionEventArgs.Exception is TargetInvocationException)
          {
              if(firstChanceExceptionEventArgs.Exception.InnerException != null)
                  MessageBox.Show(firstChanceExceptionEventArgs.Exception.InnerException.Message);
              else
                  MessageBox.Show(firstChanceExceptionEventArgs.Exception.Message);
          }
      }
      

      如果您好奇,这就是我所说的 WPF 解决方案: https://web.archive.org/web/20140809204919/https://www.tech.pro/tutorial/940/wpf-snippet-detecting-binding-errors

      【讨论】:

        【解决方案3】:

        看起来它已被报告为 Microsoft 的缺陷,他们已将其关闭为“无法修复”: ReflectPropertyDescriptor.SetValue does not preserve stack trace

        在Microsoft Reference Source 中,SetValue 方法的代码(第 1085 到 1173 行)包含一个结构如下的块:

        try     // <--- This ...
        {
            try
            {
                // Code to invoke SetMethod.
            }
            catch(Exception)
            {
                // Code to rewind.
                // Code to throw inner or rethrow.
            }
        }
        finally // <--- ... and this consume the exception before you can handle it.
        {
            // Code to raise change notification.
        }
        

        外部try ... finally 块正在消耗(第二次机会)异常,这会阻止您在代码中处理它。调试器仍然可以捕获(第一次机会)异常,但它不会退出 SetValue 方法。

        【讨论】:

        • 这个问题有点类似,但丢失堆栈跟踪与全局异常处理未捕获的异常不同。
        • @Hagelt18 查看Microsoft reference source,在我看来,它们都有相同的根本原因。第 1140 行有一个 try 语句,第 1164 行有 finally 语句,它们将有效地消耗 setter 抛出的任何异常。调试器可以设置为中断异常(第一次机会),但您永远不会在记录器中捕获它,因为第二次机会已经由 ReflectPropertyDescriptor 框架代码处理。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-06
        • 2021-12-18
        • 1970-01-01
        • 2015-04-22
        • 1970-01-01
        相关资源
        最近更新 更多