1 采用控件的 Invoke 或则BeginInvoke 来修改控件属性
Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。该属性可用于确定是否必须调用 Invoke 方法, 当不知道什么线程拥有控件时这很有用。 如果已经创建控件的句柄,则除了 InvokeRequired 属性以外,控件上还有四个可以从任何线程上安全调用的方法, 它们是:Invoke 、BeginInvoke 、EndInvoke 和 CreateGraphics 。在后台线程上创建控件的句柄之前调用 CreateGraphics 可能会导致非法的跨线程调用。对于所有其他方法调用,当从另一个线程进行调用时,
应使用这些 Invoke 方法之一。
如果控件句柄尚不存在,则 InvokeRequired 沿控件的父级链搜索,直到它找到有窗口句柄的控件或窗体为止。 如果找不到合适的句柄,InvokeRequired 方法将返回 false 。这意味着如果不需要 Invoke (调用发生在同一线程上),或者如果控件是在另一个线程上创建的但尚未创建控件的句柄, 则InvokeRequired 可以返回 false 。如果尚未创建控件的句柄,您就不能简单地在控件上调用属性、方法或事件。这可能导致在后台线程上创建控件的句柄,从而隔离不带消息泵的线程上的控件并使应用程序不稳定。 当 InvokeRequired 在后台线程上返回 false 时,您也可以通过检查 IsHandleCreated 的值来避免这种情况。如果尚未创建控件句柄,您必须等到控件句柄已创建,才能调用 Invoke 或 BeginInvoke 。通常,仅当在应用程序主窗体的构造函数中创建了后台线程时(如同在 Application.Run(new MainForm()) 中), 在已经显示窗体或取消 Application.Run 之前,才会发生这种情况。一种解决方案是等到已经创建了窗体的句柄,然后启动后台线程。通过调用 Handle 属性强制创建句柄,或者等待 Load 事件启动后台进程。 一种更好的解决方案是使用 SynchronizationContext 返回的 SynchronizationContext ,而不是使用控件进行线程间封送处理。
delegate void Hello (string name);
void helloWorld(string name)
{
this .button1.Text = name;
MessageBox .Show("Hello world " + name);
}
private void button1_Click(object sender, EventArgs e)
{
bool b = this .button1.IsHandleCreated;
if (b)
{
this .button1.BeginInvoke(new Hello (helloWorld), new string [] { "majiang" });
}
}
二采用SynchronizationContext
SynchronizationContext 类是一个基类,可提供不带同步的自由线程上下文。 此类实现的同步模型的目的是使公共语言运行库内部的异步/ 同步操作能够针对不同的异步模型采取正确的行为。此模型还简化了托管应用程序为在不同的同步环境下正常工作而必须遵循的一些要求。同步模型的提供程序可以简而言之就是允许一个线程和另外一个线程进行通讯,SynchronizationContext 在通讯中充当传输者的角色。 另外这里有个地方需要清楚的,不是每个线程都附加 SynchronizationContext 这个对象,只有UI 线程是一直拥有的。
private void mToolStripButtonThreads_Click(object sender, EventArgs e)
{
// let's see the thread id
int id = Thread .CurrentThread.ManagedThreadId;
Trace .WriteLine("mToolStripButtonThreads_Click thread: " + id);
// grab the sync context associated to this
// thread (the UI thread), and save it in uiContext
// note that this context is set by the UI thread
// during Form creation (outside of your control)
// also note, that not every thread has a sync context attached to it.
SynchronizationContext uiContext = SynchronizationContext .Current;