C#线程间操作无效: 从不是创建控件" XX" 的线程访问它

转自:http://www.arasplm.net/index.php/zh/community/myblog/c-xx-.html

前些天做的要使用到线程的项目,现在和大家分享一下感受!

以下面小列子为例,给出这个问题的解决办法。下面的列子是以一个计数器为列讲解的。

public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
// 创建线程
Thread newThread = new Thread(new ThreadStart(Count)); newThread.Start();
}
public void Count()
{
for (int i = 0; i < 100; i++)
{
lblCount.Text = i.ToString();//此时就会报出“线程间操作无效: 从不是创建控件" lblCount" 的线程访问它”;
Thread.Sleep(1000);
}
}
 
解决办法一:设置 Control.CheckForIllegalCrossThreadCalls = false;
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
// 方法一 获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 Handle 属性 // Control.CheckForIllegalCrossThreadCalls = false;
// 创建线程
Thread newThread = new Thread(new ThreadStart(Count)); newThread.Start();
}
public void Count()
{
for (int i = 0; i < 100; i++)
{
lblCount.Text = i.ToString();
Thread.Sleep(1000);
}
}
解决办法二:使用Invoke方法
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
//Invoke方法是同步的方法,所以执行过程是有先后顺序的,所以就不会出现那个异常了
//创建线程
Thread newThread = new Thread(new ThreadStart(Count));
//加上这句话,否则在关闭窗体时会出现如下错误:在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。
newThread.IsBackground = true;
newThread.Start();
}
public void Count()
{
for (int i = 0; i < 100; i++)
{
this.Invoke((EventHandler)(delegate
{
lblCount.Text = i.ToString(); }));
//这个不能放在Invoke里面,不然又Form1窗体假死情况
Thread.Sleep(1000);
}
}
解决方法三:通过BeginInvoke方法和委托来实现
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
mydelegate = new myDelegate(ShowMessage); Thread newThread = new Thread(Count);


//加上这句话,否则在关闭窗体时会出现如下错误:在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。

newThread.IsBackground = true;
newThread.Start();
}
public void Count()
{
for (int i = 0; i < 100; i++)
{ Thread.Sleep(1000);
this.BeginInvoke(mydelegate, new object[] { i });
}
}
public void ShowMessage(int i)
{
lblCount.Text = i.ToString();
}

例如:你可以理解为通过委托进行包装一个方法,最后发送调用消息。。。

        public delegate void DataReceiveHandler();
        void myProcessCom_ProcessReceivedData(object sender, ProcessReceiveDataEventArgs e)
        {
            Data = e.received_data;
            if (this.InvokeRequired)
            {
                DataReceiveHandler SetUIText = new DataReceiveHandler(ReflashText);
                this.Invoke(SetUIText);
            }
        }
        private void ReflashText()
        {
            this.richTextBox1.Clear();
            this.richTextBox1.Text = Data;
        }


以上总结:

因为第一种方法只是简单的将错误提示禁用了,仍然存在跨线程调用控件的问题。为此可能造成两个线程同时或者循环改变该控件的状态导致线程死锁。 Invoke方法是同步的方法,所以执行过程是有先后顺序的,所以就不会出现那个异常了。而第三种方法只是第二种方法的另一种形式而已,在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。

正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。


========================

c#不允许对跨线程的控件的访问,如需操作跨线程的操作需要通过委托(delegate),即是函数指针来操作跨线程。

c#不允许对跨线程的控件的访问,如需操作跨线程的操作需要通过委托(delegate),即是函数指针来操作跨线程。
 
 
delegatevoid SetListBoxCallback(string str);  //定义委托
publicvoid SetListBox(string str)
{
if (listBoxInfo.InvokeRequired) //控件是否跨线程?如果是,则执行括号里代码
{
SetListBoxCallback setListCallback = newSetListBoxCallback(SetListBox);  //实例化委托对象
listBoxInfo.Invoke(setListCallback, str);   //重新调用SetListBox函数
}
else  //否则,即是本线程的控件,控件直接操作
{
listBoxInfo.Items.Add(str);         
}
}
 


更加简单的写法,当然可以使用LAMDA语句。


调用方式2:
public voidOnFaxSended(string fax)
        {
            if(this.InvokeRequired)//是否需要跨线程调用。
            {
                FaxEventHandler dl = newFaxEventHandler(SetUI);//新建一个线程委托,回调函数为SetUI;,重新做一个线程委托
               this.Invoke(dl,fax);//本线程开始异步调用,并且将FAX参数传递回去。
            }
        }
 
        private void SetUI(string fax)
        {
            txtFax.Text += fax;
            txtFax.Text +=System.Environment.NewLine;
        }


下面错误代码:比较异同,以下是错误的,主要采用不是创建此组件的UI线程,去调用。


错误调用方式2:
public voidOnFaxSended(string fax)
        {
            if(this.InvokeRequired)//是否需要跨线程调用。
            {
                FaxEventHandler dl = newFaxEventHandler(SetUI);//新建一个线程委托,回调函数为SetUI;,重新做一个线程委托
                dl.Invoke(fax);//此处错误。,如果还使用dl,那么调用者依然是外部主体,修改为this,因为this就是form,和控件在一个线程内部。
            }
        }
 
        private void SetUI(string fax)
        {
            txtFax.Text += fax;
            txtFax.Text +=System.Environment.NewLine;
        }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值