“线程间操作无效: 从不是创建控件的线程访问它”

来源:互联网 发布:矩阵论第二版课后答案 编辑:程序博客网 时间:2024/05/18 00:50

 在编程中经常会遇到在一个按钮中执行复杂操作,并将复杂操作最后返回的值加入一个ListView或ComboBox中候选。这个时候程序会卡,当程序员将这些卡代码放进线程(Thread)中后发现当对控件操作时出现“线程间操作无效: 从不是创建控件的线程访问它”异常。
        为什么.net不让我们跨线程操作控件,这是有好处的。因为如果你的线程多了,那么当两个线程同时尝试将一个控件变为自己需要的状态时,线程的死锁就会发生。但是难道就是因为这个原因,我们就只能让程序卡着么?
当然不是

解决方案(一):用BackGroundWorker

官方参考: http://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker(VS.80).aspx

BackgroundWorker 类允许您在单独的专用线程上运行操作。耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 似乎处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker 类方便地解决问题。

若要设置后台操作,请为 DoWork 事件添加一个事件处理程序。在此事件处理程序中调用耗时的操作。若要启动该操作,请调用 RunWorkerAsync。若要收到进度更新通知,请对 ProgressChanged 事件进行处理。若要在操作完成时收到通知,请对 RunWorkerCompleted 事件进行处理。

您必须非常小心,确保在 DoWork 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。

BackgroundWorker 事件不跨 AppDomain 边界进行封送处理。请不要使用 BackgroundWorker 组件在多个 AppDomain 中执行多线程操作。

注意:需要调用 backgroundWorker1.RunWorkerAsync(); 方法来启动

常用方法:

1.RunWorkerAsync
开始执行后台操作。引发 DoWork 事件

2.CancelAsync
请求取消挂起的后台操作。
注意:这个方法是将 CancellationPending 属性设置为 true,并不会终止后台操作。在后台操作中要检查 CancellationPending 属性,来决定是否要继续执行耗时的操作。

3.ReportProgress
引发 ProgressChanged 事件。

常用属性
1.CancellationPending
指示应用程序是否已请求取消后台操作。
只读属性,默认为 false,当执行了 CancelAsync 方法后,值为 true。

2.WorkerSupportsCancellation
指示是否支持异步取消。要执行 CancelAsync 方法,需要先设置该属性为 true。

3.WorkerReportsProgress
指示是否能报告进度。要执行 ReportProgress 方法,需要先设置该属性为 true。 必须设置

常用事件
1.DoWork
调用 RunWorkerAsync 方法时发生。

2.RunWorkerCompleted
后台操作已完成、被取消或引发异常时发生。

3.ProgressChanged
调用 ReportProgress 方法时发生。

在 DoWork 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。
如果想在 DoWork 事件处理程序中和用户界面的控件通信,可在用 ReportProgress 方法。
ReportProgress(int percentProgress, object userState),可以传递一个对象。
ProgressChanged 事件可以从参数 ProgressChangedEventArgs 类的 UserState 属性得到这个信息对象。
简单的程序用 BackgroundWorker 比 Thread 方便,Thread 中和用户界面上的控件通信比较麻烦,需要用委托来调用控件的 Invoke 或 BeginInvoke 方法,没有 BackgroundWorker 方便。

详细参考: 非常实用的C# backgroundworker使用方法

 例如:

(1)在Form_Load事件中,启动backgroundWorker

        private void BP神经网络模型训练_Load(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }

       带参例子:backgroundWorker1.RunWorkerAsync(textBox1.Text);

(2)设置backgroundWorker的DoWork事件,在其中启动mainProcess();函数。注意,在该函数中不能包含操作窗体界面的代码,否则异常,而要放在ProgressChanged、或RunWorkerCompleted 事件中。

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            mainProcess();
        }

      取参数例子:string url = e.Argument.ToString();

(3)在mainProcess()函数中的某些时刻报告进度。

如果想在 DoWork 事件处理程序中和用户界面的控件通信,可在用 ReportProgress 方法。

ReportProgress(int percentProgress, object userState),可以传递一个对象。
ProgressChanged 事件可以从参数 ProgressChangedEventArgs 类的 UserState 属性得到这个信息对象。

一个参数:backgroundWorker1.ReportProgress(3);  //训练样本结束 

两个参数:bw.ReportProgress(i * 100 / 10, i);

(4)在backgroundWorker1_ProgressChanged事件中处理进度,操作界面等

 一个参数:

   private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if(e.ProgressPercentage == 1)
                label3.Text = "正在准备样本数据!";
            if (e.ProgressPercentage == 2)
                label3.Text = "正在训练样本,可能需要较长时间,请耐心等候!";
            if (e.ProgressPercentage == 3)
                label3.Text = "训练样本结束!";
        }

(5)设置WorkerReportsProgress为true,默认false,否则异常

两个参数:

progressBar1.Value = e.ProgressPercentage;
label1.Text = e.UserState.ToString();

 

常见问题:如何传递多个参数?

解答:ReportProgress(int percentProgress, object userState) 有两个参数,第一个是进度,第二个是对象类型(object),我们可以自定义一个结构体或者类来作为这个参数,从而传递多个变量。以结构体为例:

(1)定义结构体

struct UseStateStruct
    {       
        public int a;
        public double b;
        public UseStateStruct(int a, double b)
        {
            this.a = a;
            this.b = b;
        }
    }

(2)传递结构体

UseStateStruct us = new UseStateStruct(study,bp.e); //自定义
backgroundWorker1.ReportProgress(4, us);

(3)提取结构体数据

UseStateStruct us = (UseStateStruct)e.UserState;  //转换一下类型
textBox1.Text = us.a.ToString();
textBox2.Text = us.b.ToString();

 

解决方案(二):用委托

http://msdn.microsoft.com/zh-cn/library/900fyy8e(VS.80).aspx

 

参考:

教你如何解决“线程间操作无效: 从不是创建控件的线程访问它”

原创粉丝点击