在WinForm中使用多线程时,常常遇到一个问题,当在子线程(非UI线程)中修改一个控件的值:比如修改进度条进度,时会抛出如下错误
Cross-thread operation not valid: Control 'XXX' accessed from a thread other than the thread it was created on.
在VS2005或者更高版本中,只要不是在控件的创建线程(一般就是指UI主线程)上访问控件的属性就会抛出这个错误,解决方法就是利用控件提供的Invoke和BeginInvoke把调用封送回UI线程,也就是让控件属性修改在UI线程上执行
解决:
delegate void Mydelegate(object adress);
if (this.InvokeRequired)
{
}
else
{
}
关键在于if分支里的语句,首先this.InvokeRequired是控件的一个属性,它表示调用是否来自非UI线程
所以如果来自非UI线程,则需要使用控件的Invoke()或者BeginInvoke()方法来执行,其中一个是同步,一个是异步后面会仔细说两个的区别。
Invoke方法需要传入一个委托例如第一行:this.Invoke(new MethodInvoker(delegate { ChangeData(adress); }));其中MethodInvoker是.net类库里定义的表示一个委托,该委托可执行托管代码中声明为 void 且不接受任何参数的任何方法。(MSDN)delegate { ChangeData(adress); }是简洁的写法,表示传入一个参数的委托。相当于:
public delegate void Mydelegate(object adress);
Mydelegate delegate = new Mydelegate(ChangeData);
public void ChangeData(object adress)
{
...................
}
所以通过Invoke方法可以同步的将要执行的方法(ChangeData)在UI线程上执行
BeginInvoke与Invoke方法签名相同this.BeginInvoke(new Mydelegate(ChangeData),adress);注意这里传参用了和上面不同的方式,其实这两种方式在BeginInvoke与Invoke方法都可以使用
也就是在上面定义的
delegate void Mydelegate(object adress);的实现,第二个参数是要传入的参数,可以是一个对象数组
前面多次提到BeginInvoke与Invoke一个是同步执行,一个是异步执行。
简单的可以理解为,当使用Invoke时,当前线程(非UI线程)会阻塞然后UI线程执行Invoke里传入的方法,之后再继续执行当前线程。
而使用BeginInvoke时,当前线程不会阻塞,而是继续执行,当当前线程执行完毕后,UI线程才会执行BeginInvoke传入的方法。
Invoke使用了Win32API的SendMessage,
UnsafeNativeMethods.SendMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
BeginInvoke使用了Win32API的PostMessage
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);