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

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

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

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


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

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

this.cmbComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest; 

我收到了错误

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

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

有任何想法吗?

编辑:

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

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

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

答案是不。

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

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

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

虽然无法在一个线程上创建表单,并使用另一个线程显示它,但当然可以在非主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() ; } } 

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

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

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

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

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

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

我认为你的理解有点不对劲。 必须从创建它们的线程触摸控件,而不是主UI线程。 您可以在应用程序中拥有大量UI线程,每个线程都有自己的控件集。 因此,在不同的线程上创建控件将不允许您从主线程使用它,而无需使用Invoke或BeginInvoke对所有调用进行编组。

编辑多个UI线程的一些引用:

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

通常,需要从运行消息循环的同一线程访问表单的属性。 这意味着,为了在另一个线程上构造表单,您需要使用BeginInvoke封送任何实际设置属性的调用。 对于构造函数中的属性集也是如此,如果它们最终生成需要处理的消息(现在正在发生)。

即使你让它工作,它会给你带来什么? 总体来说,它会慢一点,而不是更快。

也许只是在加载此表单时显示启动画面?

或者,回顾一下为什么您的表单需要很长时间才能构建。 这需要几秒钟不常见。

我相信可以将在非UI线程上创建的组件添加到主UI,我已经完成了。

所以有2个线程,’NewCompThread’和’MainThread’。

你旋转NewCompThread并为它创建组件 – 所有这些都准备好显示在MainUI上(在MainThread上创建)。

但是……如果你在NewCompThread上尝试类似的东西,你会得到一个例外: ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;

但你可以添加这个:

 if (ComponentCreatedOnMainThread.InvokeRequired) { ComponentCreatedOnMainThread.Invoke(appropriate delegate...); } else { ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread; } 

它会工作。 我做到了
奇怪的是(对我而言)是ComponentCreatedOnNewCompTHread’认为’它是在MainThread上创建的。

如果您从NewCompThread执行以下操作: ComponentCreatedOnNewCompTHread.InvokeRequired它将返回TRUE,您将需要创建一个委托并使用Invoke返回MainThread。

可以在后台线程中创建控件,但只能在STA线程上创建。

我创建了一个扩展方法,以便将其与async / await模式一起使用

 private async void treeview1_AfterSelect(object sender, TreeViewEventArgs e) { var control = await CreateControlAsync(e.Node); if (e.Node.Equals(treeview1.SelectedNode) { panel1.Controls.Clear(); panel1.Controls.Add(control); } else { control.Dispose(); } } private async Control CreateControlAsync(TreeNode node) { return await Task.Factory.StartNew(() => CreateControl(node), ApartmentState.STA); } private Control CreateControl(TreeNode node) { // return some control which takes some time to create } 

这是扩展方法。 任务不允许设置公寓,所以我在内部使用一个线程。

 public static Task StartNew(this TaskFactory t, Func func, ApartmentState state) { var tcs = new TaskCompletionSource(); var thread = new Thread(() => { try { tcs.SetResult(func()); } catch (Exception e) { tcs.SetException(e); } }); thread.IsBackground = true; thread.SetApartmentState(state); thread.Start(); return tcs.Task; }