我在VS2005中运行的程序和直接运行可执行文件之间遇到了一个奇怪的区别。实质上,当在Application.DoEvents()
调用内的方法中抛出异常时,可以在Visual Studio中运行时捕获异常。运行已编译的可执行文件时,不会捕获异常并且程序崩溃。
这是一些简单的代码来演示这个问题。假设标准winforms样板和两个按钮和一个标签。
要运行此功能,请单击开始按钮以开始10秒计数。在10秒钟之前,按下中止按钮。并且DoEvents()
内会抛出异常。应该抓住例外。只有在Visual Studio中运行时才会发生这种情况。
private void StartButton_Click(object sender, EventArgs e) {
DateTime start = DateTime.Now;
try {
while (DateTime.Now - start < new TimeSpan(0, 0, 10)) {
this.StatusLabel.Text = DateTime.Now.ToLongTimeString();
Application.DoEvents();
}
MessageBox.Show("Completed with no interuption.");
} catch (Exception) {
MessageBox.Show("User aborted.");
}
}
private void ButtonAbort_Click(object sender, EventArgs e) {
throw new Exception("aborted");
}
我希望能够捕获这些异常。有没有办法让它发挥作用?
更新
我愿意考虑除了重新引发头痛的DoEvents()
之外的其他方法。但我还没有找到一个似乎更好的工作。我的情况是,我有一个长时间运行的循环,它控制着一些科学仪器,经常需要等待温度稳定或什么的。我想让我的用户能够中止进程,所以我有一个中止按钮,只是抛出一个自定义异常,我打算在最初启动进程的站点捕获。这似乎是一个完美的解决方案。除了由于某种原因它不起作用的事实。
如果无法使其发挥作用,是否有更好的方法?
更新2:
当我将它添加为Main()的第一行时,这使它作为可执行文件工作,但不在VS中,因此情况相反。疯狂的是,它似乎是一个无操作。我能理解这是怎么做的。
Application.ThreadException += delegate(
object sender,
System.Threading.ThreadExceptionEventArgs e
)
{ throw e.Exception; };
这太疯狂了。
答案 0 :(得分:6)
你真的 使用DoEvents
吗?它导致重新进入,这可能很难调试。
我怀疑如果你从应用程序中删除重新入侵,你将能够更容易地找出在哪里捕获异常。
编辑:是的,肯定有更好的方法。在不同的线程上执行长时间运行的任务。 UI线程应仅进行UI操作。使“中止”按钮设置一个标志,长时间运行的任务会定期检查。有关WinForms线程的示例,请参阅我的WinForms threading page volatility page表示一个线程的示例,设置另一个线程要监视的标志。编辑:我刚才记得BackgroundWorker
(我的文章没有涉及 - 它是在.NET 2.0之前编写的)有一个CancelAsync
方法 - 基本上就是这个(和{一起使用) {3}}和CancellationPending
基本上处理“有一个标志设置为取消”的东西。只需从工作线程中检查CancellationPending
,然后从您的中止中调用CancelAsync
按钮单击处理程序。
答案 1 :(得分:2)
因为DoEvents导致底层消息循环被抽取,它可能导致重新入侵(如stated in MSDN),这将使您的应用程序的行为变得不可预测。
我建议您将工作(等待温度稳定)移动到一个线程中,并在等待结束时使用某种信号告诉用户界面。这样,您的用户界面就可以保持响应状态,而无需使用Application.DoEvents。
答案 2 :(得分:1)
我认为您可以使用Application.ThreadException
事件来捕获异常。
首先要做到这一点你必须设置
在创建任何控件之前,在应用程序开始时Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
。然后为ThreadException
事件添加事件处理程序。
如果这不起作用,我建议使用BackgroundWorker
,这意味着您不需要使用DoEvents
,这也是更好的解决方案。
编辑:在您的ThreadException处理程序示例中,您将抛出另一个异常。我的想法是你将异常处理代码放在那里,而不是引发另一个异常。
但是我仍然建议您使用BackgroundWorker来执行循环代码。
答案 3 :(得分:1)
没有尝试提出旧消息,但因为我一直在努力解决同样的问题并且没有找到解决方案。如果您运行后台工作程序并且您的用户返回主应用程序线程工作并执行其他操作并且他们希望运行位于后台工作程序上的相同功能,Application.DoEvents()似乎是最好的方法,您仍然可以用它。问题在于您在RunWorkerCompleted事件中有代码。使用CancelAsync取消工作程序本身时,RunWorkerCompleted中存在的任何内容都将被执行。问题是,通常RunWorkerCompleted中的任何内容都是访问GUI而您在运行Application.DoEvents()时无法访问GUI,从RunWorkerCompmleted中删除代码并将其合并到ReportProgress函数中更为可接受。但是,您需要在reportprogress函数之前抛出检查事件,以确保主线程不请求另一次运行。在reportprogress之前就是这样的事情:
if (backgroundWorker1.CancellationPending == true){
e.Cancel = true;
return;
}
backgroundWorker1.ReportProgress(0);
所以你的后台线程正在完成它的所有工作,然后就在它之前,ReportProgress将这个检查放在那里。这样一来,如果你的用户说过嘿我真的想在另一个项目上运行这个查询,他们会在循环中等待这样的事情:
if (backgroundWorker1.IsBusy == true){
backgroundWorker1.CancelAsync();
While(backgroundWorker1.CancellationPending == true){
Application.DoEvents();
}
}
虽然这显然是一种不好的做法,但使用它可以完成摆脱异常。我一直在搜索这个,直到我开始修改我正在处理的项目,并发现错误的唯一原因是因为它同时访问主线程,我相信。可能会因此而受到抨击,但有一个答案。