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

C# 可以在后台线程上构造表单,然后在UI线程上显示,c#,winforms,multithreading,C#,Winforms,Multithreading,更新:我想总结一下我的问题: 我希望构造.NET表单和控件时不会创建任何窗口句柄——希望该过程延迟到Form.Show/Form.ShowDialog 有人能证实或否认这是真的吗 我有一个带有tab控件的大型WinForms表单,表单上有许多控件,加载时会暂停几秒钟。我已经把它缩小到InitializeComponent中设计器生成的代码,而不是构造函数或OnLoad中的任何逻辑 我很清楚,除了主UI线程外,我不能尝试在任何线程上与UI交互,但我想做的是让应用程序在后台预加载此表单(运行构造函

更新:我想总结一下我的问题:

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

有人能证实或否认这是真的吗


我有一个带有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;
}