Application.ThreadException事件未捕获绑定Property的Set方法中的异常

时间:2014-07-11 16:23:08

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

看起来属性的Set方法中出现的异常不会冒泡到Application的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();
        }
    }



}

3 个答案:

答案 0 :(得分:3)

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事件。如果启用了格式化,除AppDomain.CurrentDomain.FirstChanceException之外的其他任何地方都没有其他方法(BindingComplete除外)。

答案 1 :(得分:1)

我知道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解决方案: http://tech.pro/tutorial/940/wpf-snippet-detecting-binding-errors

答案 2 :(得分:0)

对于微软来说,它似乎已被报告为一个缺陷,微软已将其关闭为“未修复”: 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块正在消耗(第二次机会)异常,阻止您在代码中处理它。 dbugger仍然可以捕获(第一次机会)异常,但它不会退出SetValue方法。