如果这是一个简单的问题,我很抱歉(我的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是一个解决方案,因为我不希望异常一直回到第一个对话框,然后是主应用程序。我不想关闭应用程序,我只想记录它,显示一条消息,然后让它们从对话框中取消。他们可以决定从那里做什么(也许去应用程序的其他地方)。
基本上,第一个对话框和第二个对话框之间的“全局处理程序”(然后,我想,主应用程序和第一个对话框之间的另一个“全局处理程序”)。
答案 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.)
找到A打开B的代码部分。例如:
using (var b = new formB(…))
{
b.ShowDialog();
}
将来电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.
}
}
案例2:
- B相对于A是无模式的 - 也就是说,它不会阻止对A的输入;
- 您希望在一个位置捕获由B触发的所有未处理的异常;
- 您可能会或可能不希望B在导致未处理的异常时自动关闭。
找到打开B
的代码部分var b = new formB(…);
b.Show();
更改此代码,以便在专用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:
与上面的案例1类似,但您不希望B在抛出未处理的异常时自动关闭。
找到打开A的线程的入口点。除非您的Windows窗体应用程序有点花哨,否则这将是主(“UI”)线程,其入口点将是应用程序的入口点 - 对于例如,static void Main(…)
:
static void Main()
{
Application.Run(new FormA());
}
在此处为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);
}