如何在线程中打开表单并强制其保持打开状态

时间:2020-03-06 15:04:49  来源:igfitidea点击:

在弄清楚如何在这里讨论的单独的UI线程中创建Winforms时,我仍然遇到问题。

为了弄清楚这一点,我编写了以下简单的测试程序。我只是希望它在名为" UI线程"的单独线程上打开表单,并在打开表单的同时保持线程运行,同时允许用户与表单进行交互(旋转是作弊)。我了解为什么下面的操作失败并且线程立即关闭,但不确定如何解决该问题。

using System;
using System.Windows.Forms;
using System.Threading;

namespace UIThreadMarshalling {
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var tt = new ThreadTest();
            ThreadStart ts = new ThreadStart(tt.StartUiThread);
            Thread t = new Thread(ts);
            t.Name = "UI Thread";
            t.Start();
            Thread.Sleep(new TimeSpan(0, 0, 10));
        }

    }

    public class ThreadTest {
        Form _form;
        public ThreadTest() {
        }

        public void StartUiThread() {
            _form = new Form1();
            _form.Show();
        }
    }
}

解决方案

在新线程上,调用传递窗体对象的Application.Run,​​这将使线程在窗口打开时运行其自己的消息循环。

然后,我们可以在该线程上调用.Join以使主线程等待直到UI线程终止,或者使用类似的技巧来等待该线程完成。

例子:

public void StartUiThread()
{
    using (Form1 _form = new Form1())
    {
        Application.Run(_form);
    }
}

我们无法在任何线程中打开GUI表单,因为它将丢失消息泵。
我们必须通过在线程方法中调用Application.Run()来显式启动该线程中的消息泵。如果我们需要执行其他操作,则另一个选择是在循环中调用DoEvents(),因为在Application.Run()之后,该线程将在该执行点等待用户关闭表单。

private void button1_Click(object sender, EventArgs e)
{
    var t = new Thread(RunNewForm);
    t.Start();
}
public static void RunNewForm()
{
     Application.Run(new Form2());
}

我认为问题与这种想法有关:"在名为" UI线程"的单独线程上打开表单"

Windows的工作方式是这样的(请注意,Vista可能会改变其中的一些现实):

有一个重要的线程称为"主线程"或者" UI线程"。该线程是处理Windows消息的线程,例如"嘿,鼠标点击了此像素"。

这些消息进入队列,并且主线程在不忙于执行其他操作时会对其进行处理。

因此,如果我们在主线程上进行函数调用foo(),则花费的时间很长,在此期间不会处理任何Windows消息,因此不会发生用户交互。

主线程还会在屏幕上绘制UI,因此长时间运行的foo()也会阻止应用程序绘制。

除此神圣主线程和特殊主线程外,所有其他线程都是grunt worker线程。这些工作线程可以执行操作,但是它们永远无法直接与用户界面进行交互。

这种现实导致两个问题:

  • 摆脱主要威胁:由于我们不希望长时间运行的foo()停止所有用户交互,因此需要将工作交付给工作线程。
  • 返回主要内容:长时间运行的foo()完成后,我们可能希望通过在UI中执行某些操作来通知用户,但是我们无法在工作线程中执行此操作,因此我们需要"返回"到主线程。

因此,我相信我们在上述程序中的问题很笼统:目标很不正确,因为除了神圣的主线程外,其他任何线程都不能调用_form.Show()。

我认为仅调用ShowDialog而不是Show会有所帮助。问题似乎是线程在调用Show之后立即完成,在此之后Form的垃圾被回收了。 ShowDialog将暂停线程,但仍在该线程上运行表单事件,因此该线程将一直运行,直到关闭表单为止。

通常我会反过来做。若要启动长时间运行的后台任务,请在启动线程上运行表单,并启动后台线程。

我也阅读了其他问题,但无法弄清楚我们要做什么。 MVP体系结构不需要我们在不同的线程上运行业务逻辑。多线程很难做到正确,因此,如果我确实需要多线程,我只会使用多线程。