可以在后台线程上构造表单,然后在UI线程上显示

Cly*_*yde 12 c# multithreading winforms

更新:只是总结一下我的问题归结为:

我希望构建.NET表单和控件不会创建任何窗口句柄 - 希望该过程被延迟到Form.Show/Form.ShowDialog

任何人都可以确认或否认这是否属实?


我有一个带有选项卡控件的大型WinForms表单,表单上有许多控件,在加载几秒钟时会暂停.我已经将它缩小到InitializeComponent中设计器生成的代码,而不是构造函数或OnLoad中的任何逻辑.

我很清楚我不能尝试在主UI线程以外的任何线程上与UI交互,但我想做的是让应用程序预先加载这个表单(运行构造函数)在后台,所以只要用户想要打开它,就可以立即在UI线程上显示.但是,在后台线程中构建时,在设计器的这一行:

this.cmbComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
Run Code Online (Sandbox Code Playgroud)

我收到了错误

在进行OLE调用之前,必须将当前线程设置为单线程单元(STA)模式.确保您的Main函数标记了STAThreadAttribute.

现在,这是设计器文件的一半,这让我希望通常这种策略可行.但是这条特殊的线似乎试图立即启动某种OLE调用.

有任何想法吗?

编辑:

我想我不是在这里说清楚.延迟似乎发生在设计人员生成的代码期间构建bazillion控件期间.

我希望所有这些初始化代码都是在没有实际尝试触摸任何真正的Win32窗口对象的情况下发生的,因为表单尚未实际显示.

我可以设置(例如)标签文本和位置来自这个后台线程的事实让我希望这是真的.然而,对于所有房产而言可能并非如此.

Ash*_*Ash 17

虽然这是不可能在一个线程创建的形式,并使用另一个线程显示它,它绝对有可能在非主GUI线程创建的形式.目前接受的答案似乎表明这是不可能的.

Windows窗体强制执行单线程单元模型.总之,这意味着每个线程只能有一个Window消息循环,反之亦然.此外,如果例如threadA想要与threadB的消息循环交互,它必须通过诸如BeginInvoke之类的机制封送调用.

但是,如果您创建一个新线程并为其提供自己的消息循环,那么该线程将很乐意独立处理事件,直到它被告知结束消息循环.

为了演示,下面是用于在非GUI线程上创建和显示表单的Windows窗体代码:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();

    }

    private void button1_Click(object sender, EventArgs e)
    {
        ThreadStart ts = new ThreadStart(OpenForm);

        Thread t = new Thread(ts);
        t.IsBackground=false;

        t.Start(); 
    }

    private void OpenForm()
    {
        Form2 f2 = new Form2();

        f2.ShowDialog();
    }
}


public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();
    }

    private void Form2_Load(object sender, EventArgs e)
    {
        label1.Text = Thread.CurrentThread.ManagedThreadId.ToString() ;

    }
}
Run Code Online (Sandbox Code Playgroud)

OpenForm方法在新线程中运行并创建Form2的实例.

Form2实际上通过调用ShowDialog()给它自己独立的消息循环.如果您要调用Show(),则不会提供任何消息循环,并且Form2会立即关闭.

此外,如果您尝试在OpenForm()中访问Form1(例如使用"this"),则在尝试执行跨线程UI访问时将收到运行时错误.

t.IsBackground=false套线为前台线程.我们需要一个前台线程,因为在没有首先调用FormClosing或FormClosed事件的情况下关闭主窗体时会立即杀死后台线程.

除了这些要点之外,Form2现在可以像任何其他形式一样使用.您会注意到Form1仍然像往常一样快乐地运行它自己的消息lopp.这意味着您可以单击按钮并创建Form2的多个实例,每个实例都有自己独立的消息循环和线程.

您需要注意跨表单访问,现在实际上是跨线程的.您还需要确保处理主窗体的关闭,以确保正确关闭任何非主线程窗体.


dso*_*ano 5

我觉得你的理解有点偏差。必须从创建控件的线程(而不是主 UI 线程)访问控件。您可以在应用程序中拥有多个 UI 线程,每个线程都有自己的一组控件。因此,在不同线程上创建控件将不允许您在不使用 Invoke 或 BeginInvoke 编组所有调用的情况下从主线程使用它。

编辑多个 UI 线程的一些参考:

MSDN 上的消息循环 MSDN 社交讨论 WPF 中的多线程

  • 在 Windows 窗体中不能做的是让一个窗体包含 2 个控件,并且每个控件都有一个单独的线程处理事件。我认为令人困惑的是 dsolimano (正确地)将表单作为控件包含在内。从这个意义上来说,每个线程都可以有自己的Form(Control),但是放置在Form上的所有按钮等必须属于同一个线程。 (2认同)

Jan*_*ter 3

答案是不。

\n\n

如果您在 GUI 线程以外的任何线程上创建窗口句柄,则永远无法显示它。

\n\n
\n

编辑:完全可以创建表单和控件并在主 GUI 线程之外的线程中显示它们。当然,如果你这样做,你只能从创建它的线程访问多线程 GUI,但这是可能的。\xe2\x80\x93 阿什利·亨德森

\n
\n\n

您需要在 bg 线程上执行任何繁重的工作,然后将数据加载到 GUI 小部件中

\n

  • @Jan,这是不正确的。完全可以创建窗体和控件并在主 GUI 线程以外的线程中显示它们。当然,如果您这样做,您只能从创建它的线程访问多线程 GUI,但这是可能的。 (5认同)
  • 阿什是对的。我刚刚将我的应用程序切换为每个表单使用一个线程,并且它能够显示所有内容。“UI 线程”实际上意味着“您在其上创建感兴趣的 UI 实体的线程”。 (4认同)