在非UI线程中创建控件

7

我有一个插件模型,其中各种复杂的用户控件存储在DLL中,并在运行时使用加载和实例化。

Activator.CreateInstanceFrom(dllpath, classname).

由于我要加载很多这样的控件,所以我想在后台创建一个新线程来进行加载,以保持我的用户界面响应。然后将这些控件作为主窗体的子控件,并在需要时显示。
这似乎很好用,但是当我尝试设置其中一个用户控件上任何嵌套控件的属性时(例如,在按钮的事件处理程序中),就会抛出跨线程异常。我确实意识到,每次访问属性时都可以通过检查InvokeRequired来避免此问题,但我不想在编写用户控件的代码时担心这个问题(特别是因为其他人也在编写这些代码,他们可能不总是记得)。
所以我的问题是,有没有安全的方法来实现我正在尝试的操作,或者我应该如何最好地在后台加载这些控件?还是基本上不可能,我必须坚持使用主线程来创建控件?
我希望我提供的信息足以说明我的情况;如果不够清晰,我很乐意详细说明并提供代码示例。

“直到我尝试设置任何属性” - 这是在线程中还是之后? - H H
那是稍后的事情,在用户控件被添加到主窗体之后(例如form.controls.add(mycontrol))。 - Tom Juergens
3个回答

3
可以在后台加载DLL并创建控件对象,但是必须在主线程中将控件添加到表单中,并且所有用户交互以及任何程序性更改控件属性(在创建后)也必须在主线程中进行。正如您可能已经知道的那样,这是无法避免的。现在,除非加载这些DLL和控件创建需要很长时间,否则我认为没有理由在单独的后台线程中执行它们。
如果控件执行的某些操作阻塞了UI并且您希望它们在后台中发生,那么这是另一回事,您最好考虑每个操作的具体情况,并创建显式方法来在UI线程和后台线程之间通信。
试图做一个通用的、适用于UI线程模式和后台模式的框架,并简单地检查InvokeRequired的结果,有时会导致更差的性能(因为所有线程都被阻塞在Invoke中),甚至在应用程序达到合理复杂度时会导致(未检测到的)死锁。此外,所有更新都异步进行BeginInvoke,而不考虑每个方法的情况,可能会导致数据一致性问题(即,由于线程之间调用顺序的反转,控件可能会将自己更新到过去的状态)。

是的,我有一种感觉,似乎我试图变得聪明了一点。我已经放弃了我的第一次尝试,并且正在沿着与您第二段类似的方向前进。感谢您在第3段中提供的提示;我之前没有意识到这一点。 - Tom Juergens
个人而言,我更喜欢使用队列(http://msdn.microsoft.com/en-us/library/7977ey2c.aspx)在线程之间进行通信:后台线程将项目(“结果”)添加到队列中,UI 线程在空闲时检查队列并更新控件(这是典型的生产者/消费者模式)。但是您必须仔细考虑,如果后台线程无法跟上 UI 的速度,这很容易发生,特别是在网格方面,除非您采取特殊措施,否则队列会失控增长。 - Remus Rusanu

1

这个答案中的代码示例提供了一个优雅的解决方法。

引用自该答案的代码:

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;
    });        
}

我还不太清楚这会如何有所帮助。我的意思是,当然,你是对的,使用Invoke/BeginInvoke会有所帮助,但这正是我想避免的。或许我还没有理解你的意思。 - Tom Juergens
1
如果您在不同于UI线程的线程上创建控件,我无法想象您如何避免使用Invoke/BeginInvoke。然而,建议的代码比if(ctl.InvokeRequired) [...]变体要简单得多。多线程带来的直接惩罚是您需要进行同步;) - Fredrik Mörk

1

不了解InvokeRequired机制的详细信息,我进行了一些实验,据我所知,只要线程没有被父控件(即添加到某个Control.Controls属性中),就可以设置大多数线程属性。

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


编辑:我认为哪个线程创建了控件并不重要。因此,通常的规则应该适用,即您只能使用来自主Windows线程的控件。而且,我认为标准是HandleCreated而不是Parented。 只要那还没有发生,您就可以得到一点喘息时间。


是的,似乎是父控件让我很头疼。你的想法不错,但在这里不起作用,因为我设置的属性都是响应于某些用户交互之后,在其他话说就是在控件被放置到主窗体并显示之后。这可能从我的问题中没有表达得很清楚。 - Tom Juergens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接