事件的exception处理

如果这是一个简单的问题,我很抱歉(我的谷歌今天可能很糟糕)。

想象一下这个WinForms应用程序有这种类型的设计:主应用程序 – >显示一个对话框 – >第一个对话框可以显示另一个对话框。 两个对话框都有OK / Cancel按钮(数据输入)。

我试图找出某种类型的全局exception处理,沿着Application.ThreadException行。 我的意思是:

每个对话框都有一些事件处理程序。 第二个对话框可能包含:

private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) { try { AllSelectedIndexChangedCodeInThisFunction(); } catch(Exception ex) { btnOK.enabled = false; // Bad things, let's not let them save // log stuff, and other good things } } 

实际上,应该以这种方式处理此对话框中的所有事件处理程序。 这是一个例外情况,所以我只想记录所有相关信息,显示消息,并禁用该对话框的okay按钮。

但是,我想避免在每个事件处理程序中使用try / catch(如果可以的话)。 所有这些try / catch的缺点是:

 private void someFunction() { // If an exception occurs in SelectedIndexChanged, // it doesn't propagate to this function combobox.selectedIndex = 3; } 

我不相信Application.ThreadException是一个解决方案,因为我不希望exception一直回到第一个对话框,然后是主应用程序。 我不想关闭应用程序,我只想记录它,显示一条消息,让它们从对话框中取消。 他们可以决定从那里做什么(也许去应用程序的其他地方)。

基本上,第一个对话框和第二个对话框之间的“全局处理程序”(然后,我想,主应用程序和第一个对话框之间的另一个“全局处理程序”)。

是的,Application.ThreadException的默认处理是一个错误。 不幸的是,这是一个必要的错误,需要不立即劝阻和绝望成千上万的程序员编写他们的第一个Windows窗体应用程序。

你正在考虑的修复不是一个修复,它有很大的潜力使它变得更糟。 当用户单击exception对话框上的“继续”按钮是一个值得怀疑的结果时,吞噬全局exception处理程序中的exception会更糟糕。

是的, 为ThreadException编写替换处理程序。 让它在消息框中显示e.Exception.ToString()的值,以便用户知道爆炸的内容。 然后触发电子邮件或附加到错误日志,以便知道出了什么问题。 然后调用Environment.FailFast(),这样就不会再造成任何损害。

对AppDomain.CurrentDomain.UnhandledException执行相同操作。 它不会得到很多锻炼。

使用反馈来改进您的代码。 您将找到需要validation的位置。 您可以帮助客户的IT人员通过LAN和设备诊断故障。 并且您会发现极少数情况下您自己的try / catch块可能能够从exception中恢复。

您可以使用AppDomain.CurrentDomain.UnhandledException处理程序拦截主UI线程上的错误并按对话框处理它们。 来自MSDN:

在使用Windows窗体的应用程序中,主应用程序线程中的未处理exception会导致引发Application.ThreadException事件。 如果处理此事件,则默认行为是未处理的exception不会终止应用程序,尽管应用程序处于未知状态。 在这种情况下,不会引发UnhandledException事件。 可以通过使用应用程序配置文件或使用Application.SetUnhandledExceptionMode方法在连接ThreadException事件处理程序之前将模式更改为UnhandledExceptionMode.ThrowException来更改此行为。 这仅适用于主应用程序线程。 针对在其他线程中引发的未处理exception引发UnhandledException事件。

如果您在可能引发exception的combobox事件处理程序中执行操作,您可能希望稍微重新考虑应用程序的设计。

另一种方法是在向用户显示对话框之前,使用所需的所有信息初始化对话框。 然后用户进行选择,然后按OK,然后父对话框可以处理对话框中的信息。

然后可以在父对话框中完成exception处理。

当然,如果您需要根据用户操作动态更新对话框中的数据,这将不合适……

例如

 MyDialog myDialog = new MyDialog(); myDialog.Init(//data for the user to choose/manipulate); if(myDialog.ShowDialog() == DialogResult.OK) { try{ ProcessDialogData(myDialog.SomeDataObject); } catch(/*...*/} } 

HTH

WinForms应用程序中的全局exception处理使用两个处理程序完成:Application.ThreadException和AppDomain.CurrentDomain.UnhandledException。 ThreadException捕获主应用程序线程中的未处理exception,而CurrentDomain.UnhandledException捕获所有其他线程中的未处理exception。 全局exception处理可用于以下目的:显示用户友好的错误消息,记录堆栈跟踪和其他有用信息,清理,向开发人员发送错误报告。 在捕获未处理的exception之后,应该终止应用程序。 您可能想要重新启动它,但是至少在非平凡的应用程序中无法纠正错误并继续。

全局exception处理不能替代仍应使用的本地exception处理。 本地exception处理程序永远不应该使用catchexception,因为这有效地隐藏了编程错误。 在每种情况下都必须只捕获预期的exception。 任何意外的exception都会导致程序崩溃。

听起来你想要方面。 PostSharp可以帮助你。

这可以完成,但根据您的表单是以模态还是无模式显示,可以使用不同的技术。 (模态表单阻止所有用户输入到父表单,而无模式表单则不会。)

在下面的答案中,我将假设两种forms,A和B.A是在某个时刻打开B的父表单。

请注意,我没有深入测试下面列出的解决方案。 而不是复制和粘贴代码,尝试理解它们,然后采取并适应在您的情况下似乎适用的东西。


情况1:

  • B是关于A的模态 – 即B阻止用户输入A;
  • 您希望在一个位置捕获由B触发的所有未处理的exception
  • 你希望B在导致未处理的exception时自动关闭。
    (如果您不想这样做,请进一步参考案例3。)
  1. 找到A打开B的代码部分。例如:

     using (var b = new formB(…)) { b.ShowDialog(); } 
  2. try / catch块中包含对b.ShowDialog()的调用。 由B中的事件处理程序触发的未捕获exception将冒泡到此处,因此您可以在此处理它们:

     using (var b = new FormB(…)) { try { formB.ShowDialog(); } catch (…Exception ex) { MessageBox.Show("B made a boo-boo."); // don't bother doing something to B, since the end of the `using` // block, and the fact that execution has left `b.ShowDialog()`, // will force it to close and dispose. } } 

案例2:

  • B对于A是无模式的 – 也就是说,它不阻止对A的输入;
  • 您希望在一个位置捕获由B触发的所有未处理的exception;
  • 您可能会或可能不希望B在导致未处理的exception时自动关闭。
  1. 找到打开B的代码部分

     var b = new formB(…); b.Show(); 
  2. 更改此代码,以便在专用STA线程上显示B实例化。 此外,在该线程上安装Application.ThreadException的处理Application.ThreadException ,并处理B中所有未捕获的exception:

     var thread = new Thread(() => { Form b = null; Application.ThreadException =+ (sender, e) => { Exception ex = e.Exception; MessageBox.Show("B made a booboo."); if (b != null) { … // do something with B here if you want; // eg close it via a b.Close(), // or force it to close via Application.ExitThread(). } } b = new FormB(); Application.Run(b); }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); 

    您可能希望将样板代码移动到参数化的辅助方法,例如具有签名的方法,例如void ShowAndCatch(Func

    formFactory, Action exceptionHandler)


案例3:

与上面的案例1类似,但您不希望B在抛出未处理的exception时自动关闭。

  1. 找到打开的线程的入口点A.除非您的Windows窗体应用程序有点花哨,否则这将是主(“UI”)线程,其入口点将是应用程序的入口点 – 例如, static void Main(…)

     static void Main() { Application.Run(new FormA()); } 
  2. 在此处为Application.ThreadException安装处理程序,并确保将未处理的exception转发到此事件处理程序:

     static void Main() { Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.ThreadException += (sender, e) => { // The tricky part here is how to determine whether the exception // was triggered by B or by any other part of your application. // One possibly working technique might be: if (e.Exception.WasTriggeredByAFormB()) // see extension method below { MessageBox.Show("B made a boo-boo."); // Unfortunately, we are unlikely to have a direct reference to B // here. If it is guaranteed that there is only one form of type // FormB, you could retrieve it eg via: var b = Application.OpenForms.OfType().SingleOrDefault(); if (b != null) { … // do something to b. } } else { … // other unhandled exception; perhaps do a Environment.FailFast(null); } } Application.Run(new FormA()); } public static bool WasTriggeredByAFormB(this Exception exception) { return new StackTrace(e.Exception) .GetFrames() .Select(frame => frame.GetMethod().DeclaringType) .FirstOrDefault(type => typeof(Form).IsAssignableFrom(type)) == typeof(FormB); }