在非UI线程中创建控件

时间:2009-06-24 20:24:49

标签: .net winforms multithreading controls

我有一种插件模型,其中各种复杂的用户控件存储在DLL中,并在运行时使用

加载和实例化
Activator.CreateInstanceFrom(dllpath, classname).

由于我正在加载其中的一些,我想在后台执行此操作,通过创建新线程来执行加载来保持我的UI响应。然后,控件将作为主窗体的父级,并在需要时显示。

这似乎工作正常 - 直到我尝试在其中一个用户控件上的任何嵌套控件上设置任何属性,例如在按钮的事件处理程序中,它抛出一个交叉线程异常。我确实通过每次访问属性时检查InvokeRequired都可以避免这种情况,但是在编写用户控件的代码时我更不用担心(特别是因为还有其他人编写这些代码也可能不要总记得。)

所以我的问题是,有没有什么安全的方法来做我正在尝试的事情,或者我应该如何最好地在后台加载这些控件?或者它基本上是不可能的,我是否必须坚持主线程来创建控件?

我希望我提供的信息足以让我的情况变得清晰;如果没有,我很乐意详细说明并提供代码示例。

3 个答案:

答案 0 :(得分:3)

可以在后台加载DLL并创建控件对象,但是必须将控件添加到主线程中的表单和所有用户交互以及控件属性的任何编程更改(在创建之后) )必须发生在主线程中。你可能已经知道,根本无法解决这个问题。现在除非那些DLL和控件创建的加载需要很长时间,否则我认为没有理由在单独的后台线程中执行它。

如果控件执行的某些操作阻止了UI并且您希望它们在后台发生,那么这是一个不同的故事,您最好逐个考虑每个操作并创建显式方法以在UI线程和UI之间进行通信背景线程。

尝试在UI线程模式和后台模式下执行通用的一对一框架,并且只在更差性能中检查InvokeRequired结果(因为所有线程都被阻止)应用程序达到合理的复杂度后,立即调用(或调用)或(未检测到)死锁。同时在BeginInvoke中执行所有更新异步,没有单独考虑每个方法,可能会导致数据一致性问题(即,由于线程之间的调用顺序的反转,控件可能会及时将自身更新为状态 back )。

答案 1 :(得分:1)

this answer中的代码示例提供了解决此问题的优雅方法。

该答案引用的代码:

public void UpdateTestBox(string newText)
{
    BeginInvoke((MethodInvoker) delegate {
        tb_output.Text = newText;
    });        
}

...虽然在你的情况下你想要在控件本身上调用BeginInvoke:

public void UpdateTestBox(string newText)
{
    tb_output.BeginInvoke((MethodInvoker) delegate {
        tb_output.Text = newText;
    });        
}

答案 2 :(得分:1)

我不知道InvokeRequired机制的细节,我一直在试验一下,只要它没有Parented(即添加到某些Control.Controls属性),你就可以在Thread中设置大多数属性。

因此,您应该能够准备控件,将它们存储在列表中,并将它们以Invoked方法附加到主UI。


编辑:我认为哪个Thread创建了一个Control并不重要。因此应该应用正常的规则,即您只能使用主Windows线程的控件。我认为标准是HandleCreated而不是Parented。只要没有发生这种情况,你就会得到一点喘息。