您的位置:首页 > 编程语言 > C#

C#跨线程操作控件

2015-09-14 21:14 477 查看
问题:

在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)

{

    this.Invoke(new MethodInvoker(delegate { ChangeData(adress); }));

    //this.BeginInvoke(new Mydelegate(ChangeData),adress);

}

else

{

    ChangData(adress);

}

关键在于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);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c# ui 线程