2

我在 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");
    }

我希望能够捕获这些异常。有什么办法让它工作吗?

更新:

我愿意考虑 re-entrant-headache-inducing 以外的其他方法DoEvents()。但我还没有找到一个似乎更好的工作。我的情况是我有一个长时间运行的循环来控制一些科学仪器,并且经常需要等待温度稳定或其他什么。我想让我的用户能够中止进程,所以我有一个中止按钮,它只是抛出一个自定义异常,我打算在最初启动进程的站点上捕获它。这似乎是一个完美的解决方案。除了由于某种原因它不起作用的事实。

如果无法使其正常工作,是否有更好的方法?

更新 2:

当我将它作为 Main() 的第一行添加时,它可以作为可执行文件工作,但不能在 VS 中工作,所以情况正好相反。疯狂的是,它似乎是无操作的。我能理解这是怎么做的。

Application.ThreadException += delegate(
        object sender, 
        System.Threading.ThreadExceptionEventArgs e
    ) 
    { throw e.Exception; };

疯了吧。

4

4 回答 4

6

你真的必须使用DoEvents吗?它会导致重入,这可能很难调试。

我怀疑如果您从应用程序中删除重入,您将能够更轻松地找出捕获异常的位置。

编辑:是的,肯定有更好的方法来做到这一点。在不同的线程上执行您的长时间运行的任务。UI 线程应该执行 UI 操作。让您的“中止”按钮设置一个标志,长期运行的任务会定期检查该标志。有关 WinForms 线程的示例,请参阅我的WinForms 线程页面,以及一个线程设置标志以被另一个线程监视的示例的易失性页面。

编辑:我刚刚记得BackgroundWorker(我的文章没有涵盖 - 它是在.NET 2.0 之前编写的)有一个CancelAsync方法 - 基本上这个(与 the 一起使用CancellationPending并且WorkerSupportsCancellation基本上处理“有一个标志设置为取消” 给你的东西。只需CancellationPending从工作线程中检查,然后CancelAsync从你的中止按钮单击处理程序中调用。

于 2009-03-17T19:55:14.070 回答
2

因为DoEvents会导致底层消息循环泵送,它可能导致重新进入(如MSDN 中所述),这将使您的应用程序的行为不可预测。

我建议您将您的工作(等待温度稳定)转移到一个线程中,并使用某种信号来告诉您的用户界面何时等待结束。这将允许您的用户界面保持响应,而无需使用Application.DoEvents

于 2009-03-17T20:22:00.447 回答
1

我认为您也许可以使用该Application.ThreadException事件捕获异常。

首先,您必须 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);在应用程序开始时进行设置,然后再创建任何控件。然后为该事件添加事件处理程序ThreadException

如果这不起作用,我建议您使用 aBackgroundWorker这意味着您不需要使用DoEvents它,这也是更好的解决方案。

编辑:在您的 ThreadException 处理程序示例中,您正在引发另一个异常。这个想法是你把你的异常处理代码放在那里,而不是引发另一个异常。

但是,我仍然敦促您使用 BackgroundWorker 来执行您的循环代码。

于 2009-03-17T20:24:26.917 回答
1

不是试图提出旧消息,而是因为我一直在努力解决同样的问题并且没有找到解决方案。如果您正在运行后台工作程序并且您的用户回到主应用程序线程中工作并做其他事情并且他们想要运行位于后台工作程序上的相同功能,那么 Application.DoEvents() 似乎是最好的方法,您仍然可以用它。问题在于您在 RunWorkerCompleted 事件中有代码。当您使用 CancelAsync 取消工作人员本身时, RunWorkerCompleted 中存在的任何内容都将被执行。问题是,通常您的 RunWorkerCompleted 中的任何内容都在访问 GUI,而您在运行 Application.DoEvents() 时无法访问 GUI,从 RunWorkerCompmlted 中删除您的代码并将其合并到 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();
     }
}

虽然这显然是一种不好的做法,但使用它可以消除异常。我到处搜索,直到我开始修改我正在处理的项目,发现它出错的唯一原因是因为它同时访问主线程,我相信。可能会因此而受到抨击,但有一个答案。

于 2010-07-01T01:20:14.667 回答