事件的异常处理

时间:2010-03-26 15:23:27

标签: c# winforms exception-handling

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

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

我试图找出某种类型的全局异常处理,与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是一个解决方案,因为我不希望异常一直回到第一个对话框,然后是主应用程序。我不想关闭应用程序,我只想记录它,显示一条消息,然后让它们从对话框中取消。他们可以决定从那里做什么(也许去应用程序的其他地方)。

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

6 个答案:

答案 0 :(得分:8)

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

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

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

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

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

答案 1 :(得分:1)

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

  

在使用Windows的应用程序中   表格,未处理的例外情况   主应用程序线程导致   Application.ThreadException事件   待提高。如果这个事件是   处理,默认行为是   未处理的异常没有   但是,终止申请   该应用程序是一个未知的   州。在那种情况下,   UnhandledException事件不是   提高。可以更改此行为   通过使用应用程序配置   文件,或使用   Application.SetUnhandledExceptionMode   将模式更改为的方法   UnhandledExceptionMode.ThrowException   在ThreadException事件之前   处理程序被连接起来。这适用   仅限主应用程序线程。   引发UnhandledException事件   抛出未处理的异常   其他线程。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

如果您正在使用可能引发异常的组合框事件处理程序,您可能会稍微重新考虑应用程序的设计。

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

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

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

e.g。

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

HTH

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

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

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

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


  

案例1:

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

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

    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.
        }
    }
    

  3.   

    案例2:

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

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

      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;
                    // e.g. 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<Form> formFactory, Action<Exception> exceptionHandler)

    3. 等签名的签名
        

      案例3:

           

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

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

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

        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 e.g. via:
                    var b = Application.OpenForms.OfType<FormB>().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);
        }