最近在写一个东西时,需要在多线程中操作非本线程创建的控件,尝试直接调用时报错(线程间操作无效,从不是创建控件的线程访问它,InvalidOperationException),经查询可知,c#默认不允许其他线程直接操作非其创建的控件。那怎么办呢?不用着急,通过下面这个小例子,即可了解到如何解决此问题。

先来看看在同一个线程中操作控件

建立一个简单的窗口,只有一个textbox和一个button。
这个窗口类拥有一个方法,该方法的目的是向文本框中加入测试二字。

private void addText()
{
    this.textBox1.Text += "\r\n测试";
}

在按钮的click事件中调用该方法。

private void button1_Click(object sender, EventArgs e)
{
    this.addText();
}

实际效果,可见在同一个线程中操作控件无任何问题.

再来看看在不同线程中操作时

窗口还是那个窗口,通过设置一个timer来模拟在不同线程中调用addtext方法的情况
先定义方法。

private void timerAddText()
{
    System.Timers.Timer t = new System.Timers.Timer(2000);
    t.Elapsed += new System.Timers.ElapsedEventHandler(callme);
    t.AutoReset = true;
    t.Enabled = true;
}
public void callme(object source, System.Timers.ElapsedEventArgs e)
{
    addText();
}

然后在该窗体类的构造方法中调用方法,即在构造方法中加一条

TimerAddText();

尝试执行,报错:线程间操作无效,从不是创建控件的线程访问它,InvalidOperationException。

解决方法

为什么会报错呢,借用别人的一句话(http://blog.csdn.net/lnstree/article/details/7883031)

c#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的。当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它,此时它将会在内部调用new MethodInvoker(LoadGlobalImage)来完成下面的步骤,这个做法保证了控件的安全,你可以这样理解,有人想找你借钱,他可以直接在你的钱包中拿,这样太不安全,因此必须让别人先要告诉你,你再从自己的钱包把钱拿出来借给别人,这样就安全了。

那么我们该如何做呢?
先了解一下下面这两个东西

控件的InvokeRequired属性

这是vs中的摘要:

获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。
返回结果:
如果控件的 System.Windows.Forms.Control.Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke方法对控件进行调用),则为 true;否则为 false。

控件的invoke方法

函数原型1

public object Invoke(Delegate method);

摘要:
在拥有此控件的基础窗口句柄的线程上执行指定的委托。
参数:
method:
包含要在控件的线程上下文中调用的方法的委托。
返回结果:
正在被调用的委托的返回值,或者如果委托没有返回值,则为 null。

函数原型2

public object Invoke(Delegate method, params object[] args);

摘要:
在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。
参数:
method:
一个方法委托,它采用的参数的数量和类型与 args 参数中所包含的相同。
args:
作为指定方法的参数传递的对象数组。如果此方法没有参数,该参数可以是 null。
返回结果:
System.Object,它包含正被调用的委托返回值;如果该委托没有返回值,则为 null。

看到这里大概有了一个思路,即在操作控件时,通过InvokeRequired属性来判断当前线程是否能够直接操作该控件,如果不能(值为true),则通过Invoke()来让该控件的爹爹(即创建控件的线程)操作控件。
添加下面一行,即定义一个委托。

//定义一个委托
private delegate void delegateAddText();

然后将timer绑定的方法(这里是callme)的方法体修改为如下

public void callme(object source, System.Timers.ElapsedEventArgs e)
{
    //判断当前进程是否有权对该空间进行操作
    if (textBox1.InvokeRequired)
    {
        //通过invoke使控件它爹(原线程)执行该委托,即addtext
        textBox1.Invoke(new delegateAddText(addText));
    }
    else
    {
        //有权操作的话直接调用即可
        addText();
    }
}

之后再次尝试运行,问题得到解决。

引用资料

1.[头图] 【图片】 化物语 战场原黑仪
2.[资料] 【CSDN】Lnstree C#多线程问题

我来吐槽

*

*