【问题标题】:Do I have to call Application.ExitThread()?我必须调用 Application.ExitThread() 吗?
【发布时间】:2016-06-06 10:55:00
【问题描述】:
using System.Windows.Forms;

public class App
{
    [STAThread]
    public static void Main()
    {
        string fname;
        using (var d = new OpenFileDialog())
        {
            if (d.ShowDialog() != DialogResult.OK)
            {
                return;
            }
            fname = d.FileName;
        }
        //Application.ExitThread();
        for (; ;)
            ;
    }
}

上面的代码显示了一个文件对话框。一旦我选择了一个文件并按下open,for 循环就会执行,但(冻结的)对话框仍然存在。

一旦我取消注释Application.ExitThread(),对话框就会按预期消失。

这是否按预期工作?为什么using 不让窗口消失?在哪里可以找到有关此的更多信息?

【问题讨论】:

  • 我已经测试了您的代码,(文件打开)对话框按预期正确关闭。
  • 处理完后调用Application.DoEvents(),对话框会消失吗?
  • 没有人会从那个 sn-p 重现你的问题。在一个 GUI 应用程序中,永远不要用任何像 for(;;); 这样的暂停和捕获代码来挂起主线程,这一点至关重要。很多事情都停止了,尤其是绘画不再发生。因此,窗口中的任何像素都无法更新,您可能会看到窗口顶部显示的任何内容的幻影。花时间读一本像样的教程或书籍,如何正确编写 GUI 代码很少通过反复试验发现。
  • 考虑诊断。您的代码不需要调用 DoEvents()。
  • 您应该在问题中提到这一点。无论如何,它不会改变问题。 single-threaded apartment (STA) thread 需要有一个消息循环。只要它在屏幕上,对话框就会通过ShowDialog 方法在内部运行一个。但是一旦用户关闭它,消息循环就会消失。没有人再泵送消息,违反 STA 线程的条件。事实上,您的应用程序已经变得愚蠢,因为它被锁定在一个紧密的无限循环中。

标签: c# .net winforms dialog mono


【解决方案1】:

您已经发现单线程应用程序的主要问题...长时间运行的操作会冻结用户界面。

您的DoEvents() 调用本质上是“暂停”您的代码并为其他操作(如 UI)提供运行机会,然后继续。问题是您的 UI 现在再次冻结,直到您再次调用 DoEvents()。实际上,DoEvents()very problematic approach (some call it evil)。你真的不应该使用它。

你有更好的选择。

将长时间运行的操作放在另一个线程中有助于确保 UI 保持响应速度并尽可能高效地完成工作。处理器能够在两个线程之间来回切换,给人一种同时执行的错觉,而没有成熟的多进程的困难。

实现此目的的一种更简单的方法是使用BackgroundWorker,尽管它们通常已经失宠(出于我不打算在这篇文章中讨论的原因:further reading)。然而,它们仍然是 .NET 的一部分,学习曲线比其他方法要低,所以我仍然建议新开发人员在业余项目中使用它们。

目前最好的方法是 .NET 的 Tasks 库。如果您的长时间运行的操作已经在一个线程中(例如,它是一个数据库查询并且您只是在等待它完成),并且如果库支持它,那么您可以使用 async 关键字和不必三思而后行。即使它尚未在线程或受支持的库中,您仍然可以启动一个新任务并通过Task.Run() 在单独的线程中执行它。 .NET 任务具有内置语言支持等优势,例如协调多个任务并将任务链接在一起。

【讨论】:

  • 我想说 DoEvents 的问题比您的回答所显示的要多且严重。此外,“单线程应用程序”的事情是一个红鲱鱼。所有 GUI 工作都将在单个线程上完成。应用程序具有单个 UI 线程是一项硬性要求。当然,您可以使用诸如 BackgroundWorker 之类的东西来解决这个问题,但要做的就是从后台线程调用 UI 线程。它在许多情况下都有效,但并非在所有情况下都有效。只要您尝试更新 UI,Tasks 就会遇到同样的问题。
  • @CodyGray - 我删除了有关 DoEvents 的部分并链接到更多信息。老实说,我多年来没有使用过 DoEvents 来支持异步,并且忘记了它实际上有多大问题。
  • @CodyGray - 虽然我仍然不同意您对“单线程应用程序”和“所有 GUI 工作都将在单个线程上完成”的混淆。在单线程应用程序中,所有工作、GUI 和其他所有工作都在单个线程上完成,这是问题所在 - OP 需要一些后台线程来执行他/她的长时间运行操作(这将使应用程序多线程
  • 我实际编写的应用程序(我的问题代码只是一个小例子)一旦我选择了我想打开的文件,就会停止使用 winforms。因此,中断 GUI 的职责并不是一件长期存在的事情——它是“另一段旅程的开始”,也是 winforms “刷新所有东西”的好时机。
  • @alex 请阅读链接的材料(尤其是“非常有问题的方法”),这样你就知道你在做什么...... DoEvents 可能会产生非常严重的副作用。
【解决方案2】:

JDB 已经在his answer 中解释了为什么(一般来说)您的代码不能按预期工作。让我添加一点建议解决方法(针对您的特定情况以及当您只需要使用系统对话框然后像控制台应用程序一样继续操作时)。

You're trying 使用Application.DoEvents(),好吧,它似乎可以工作,在您的情况下,您没有可重入代码。但是,您确定所有相关消息都已正确处理吗?您应该拨打多少次Application.DoEvents()?你确定你正确地初始化了一切吗(我说的是ApplicationContext)?第二个问题比较务实,OpenFileDialog 需要 COM,COM(这里)需要 STAThreadSTAThread 需要消息泵。我无法告诉你它会以何种方式失败,但可以肯定它可能会失败。

首先请注意,应用程序通常使用Application.Run() 启动主消息循环。你不希望看到new MyWindow().ShowDialog(),对吧?您的示例没有什么不同,让Application.Run(Form) 重载为您创建ApplicationContext(并在表单关闭时处理HandleDestroyed 事件,最终将调用-惊喜-Application.ExitThread())。不幸的是,OpenFileDialog 没有从 Form 继承,那么您必须将其托管在 dummy 表单中才能使用 Application.Run()

如果您使用设计器在表单内添加对话框,则无需显式调用dlg.Dispose()(让 WinForms 管理对象生命周期)。

using System;
using System.Windows.Forms;

public class App
{
    [STAThread]
    public static void Main()
    {
        string fname = AskForFile();
        if (fname == null)
            return;

        LongRunningProcess(fname);
    }

    private static string AskForFile()
    {
        string fileName = null;

        var form = new Form() { Visible = false };
        form.Load += (o, e) => { 
            using (var dlg = new OpenFileDialog())
            {
                if (dlg.ShowDialog() == DialogResult.OK)
                    fileName = dlg.FileName;
            }

            ((Form)o).Close();
        };

        Application.Run(form);

        return fileName;
    }
}

【讨论】:

  • OpenFileDialog 似乎不能转换为 ApplicationContext。
  • 为什么必须这样做?如果您想模仿(出于学习目的,没有真正的理由这样做) Application.Run() 所做的,那么选择反汇编程序或直接公开源代码。你会看到有更多你可能期望的代码
  • 我的意思是:似乎需要将 OpenFileDialog 强制转换为 ApplicationContext 才能运行您的代码——但这不起作用。 (我将您的代码复制并粘贴到我的 IDE 中,因此无法运行它。)
  • “你不需要显式调用 dlg.Dispose()” 我不确定你的意思是什么,但无论如何我必须挑剔。如果您以模态方式显示对话框,则绝对确实需要调用 Dispose 方法。您在这里所做的,将其创建包装在 using 语句中在道德上是等效的,但更可取。
  • @CodyGray 当然你需要调用任何IDisposable 成员的.Dispose()(和using 是等价的),我的意思是(抱歉不够清楚)是你使用 WinForms 设计器在表单中插入对话框时不需要显式调用。
【解决方案3】:

不,您不必致电Application.ExitThread()

Application.ExitThread() 终止调用线程的消息循环并强制销毁冻结的对话框。虽然“可行”,但如果知道冻结的原因,最好解冻对话框。

在这种情况下,按下open 似乎会触发一个没有任何机会完成的关闭事件。 Application.DoEvents() 给了它机会,让对话框消失。

【讨论】:

  • DoEvents 在大多数情况下都是不好的建议。关于为什么 Sleep(或有问题的忙等待)冻结 UI(即stackoverflow.com/questions/27356165/…)以及如何使用计时器、后台工作人员或async 代码正确处理此类情况,已有大量讨论。
  • 小心,你在这里从煎锅里跳进火里。您已将 ExitThread(一个坏主意)替换为对 DoEvents 的调用(可以说是一个更糟糕的主意)。两者都是非常糟糕的黑客行为,证明您不了解问题所在或设计应用程序的正确方法,因此它不存在该问题。我真的不能给你更多的帮助,因为你的问题完全没有描述你真正想要完成的事情。显然,现实世界中没有任何程序使用无限 for (;;) 循环。它永远不会在 GUI 应用程序中工作。
  • 我实际编写的应用程序(我的问题代码只是一个最小的示例)一旦我选择了我想打开的文件,就会停止使用 winforms。因此,中断 GUI 的职责并不是一件长期存在的事情——它是“另一段旅程的开始”,也是 winforms “刷新所有东西”的好时机。
猜你喜欢
  • 1970-01-01
  • 2015-08-26
  • 2017-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-05
  • 1970-01-01
  • 2011-12-13
相关资源
最近更新 更多