C# 可以在后台线程上构造表单,然后在UI线程上显示
更新:我想总结一下我的问题: 我希望构造.NET表单和控件时不会创建任何窗口句柄——希望该过程延迟到Form.Show/Form.ShowDialog 有人能证实或否认这是真的吗C# 可以在后台线程上构造表单,然后在UI线程上显示,c#,winforms,multithreading,C#,Winforms,Multithreading,更新:我想总结一下我的问题: 我希望构造.NET表单和控件时不会创建任何窗口句柄——希望该过程延迟到Form.Show/Form.ShowDialog 有人能证实或否认这是真的吗 我有一个带有tab控件的大型WinForms表单,表单上有许多控件,加载时会暂停几秒钟。我已经把它缩小到InitializeComponent中设计器生成的代码,而不是构造函数或OnLoad中的任何逻辑 我很清楚,除了主UI线程外,我不能尝试在任何线程上与UI交互,但我想做的是让应用程序在后台预加载此表单(运行构造函
我有一个带有tab控件的大型WinForms表单,表单上有许多控件,加载时会暂停几秒钟。我已经把它缩小到InitializeComponent中设计器生成的代码,而不是构造函数或OnLoad中的任何逻辑 我很清楚,除了主UI线程外,我不能尝试在任何线程上与UI交互,但我想做的是让应用程序在后台预加载此表单(运行构造函数),以便在用户想要打开它时立即在UI线程上显示它。但是,在后台线程中构造时,在设计器中的此行上:
this.cmbComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
我发现了错误
当前线程必须设置为单线程
OLE之前的线程单元(STA)模式
可以打电话。确保您的
Main函数具有stathreadsattribute属性
上面有记号
现在这是designer文件的一半,这给了我希望,总的来说,这个策略会起作用。但这条特别的线路似乎试图立即启动某种OLE调用
有什么想法吗
编辑:
我想我没有说清楚。延迟似乎发生在设计器生成代码期间的大量控件的构造过程中
我的希望是,所有这些初始化代码都是在没有实际接触任何真正的Win32窗口对象的情况下进行的,因为表单还没有实际显示出来
事实上,我可以设置(例如)标签文本和位置从这个背景线程给了我希望,这是真的。但是,并非所有属性都是如此。通常,表单的属性需要从运行消息循环的同一线程访问。这意味着,为了在另一个线程上构造表单,需要使用BeginInvoke封送任何调用以实际设置属性。如果构造函数中的属性集最终生成需要处理的消息(就像现在发生在您身上的那样),那么它们也是如此 即使你成功了,它能给你带来什么?总的来说,它会慢一点,而不是快一点 可能只是在加载此表单时显示启动屏幕
或者,首先回顾一下为什么表单需要如此长的时间来构建。这需要几秒钟的时间是不常见的。我认为你的理解有点偏差。控件必须从创建它们的线程(而不是主UI线程)进行触摸。一个应用程序中可能有许多UI线程,每个线程都有自己的控件集。因此,如果不使用Invoke或BeginInvoke对所有调用进行编组,那么在不同线程上创建控件将不允许您从主线程使用它 编辑 多UI线程的一些参考: 答案是否定的 如果在GUI线程以外的任何线程上创建窗口句柄,则永远无法显示它 编辑:完全可以创建窗体、控件和 在主GUI线程以外的线程中显示它们。当然如果 执行此操作时,只能从线程访问多线程GUI 这创造了它,但它是可能的阿什利·亨德森
您需要在bg线程上执行任何繁重的操作,然后将数据加载到GUI小部件中虽然不可能在一个线程上创建表单,并使用另一个线程显示表单,但当然也可以在非主GUI线程中创建表单。目前公认的答案似乎是,这是不可能的 Windows窗体强制执行单线程单元模型。总之,这意味着每个线程只能有一个窗口消息循环,反之亦然。此外,例如,如果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现在可以像其他任何形式一样使用。您会注意到Form1仍然像往常一样愉快地运行,它有自己的消息lopp。这意味着您可以单击按钮并创建多个Form2实例,每个实例都有各自独立的消息循环和线程
您确实需要注意跨表单访问,它现在实际上是跨线程的。您还需要确保处理主窗体的关闭,以确保正确关闭任何非主线程窗体。我相信可以将在非UI线程上创建的组件添加到主UI,我已经完成了 所以有两个线程,“NewCompThread”和“MainThread” 您派生了NewCompThread,它为您创建组件—所有组件都准备好显示在M上
if (ComponentCreatedOnMainThread.InvokeRequired) {
ComponentCreatedOnMainThread.Invoke(appropriate delegate...);
} else {
ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;
}
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<T> StartNew<T>(this TaskFactory t, Func<T> func, ApartmentState state)
{
var tcs = new TaskCompletionSource<T>();
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;
}