跨线程修改UI控件

前些天,有个朋友让我给他演示下C#,好快速入门,以完成某科目的期末大作业。然后,就涉及到一个进度条的东西,紧接着就涉及到跨线程修改控件的问题了。

因为从来没用过,所以,华丽丽的跪了。只在网上搜到一个异步的方式,但是,此异步是使用控件的异步,仍然会导致UI线程的卡顿。

经过几天的寻找资料,大致可以得到下面几种方法:

1.直接在线程创建线程中,对是否是创建线程访问控件不做检查,直接跨线程操作。

首先,在创建线程中,添加语句:

 CheckForIllegalCrossThreadCalls = false;
然后,定义按钮事件:

private void startButton_Click(object sender, EventArgs e)
        {
            CheckForIllegalCrossThreadCalls = false;
            progressBar1.Maximum = 50;
            myThread = new Thread(canCrossThreadCall); myThread.Start();
        }

这里习惯性的自己控制线程。下面是线程中的操作,以及结束按钮事件:

private void canCrossThreadCall()
        {
            progressBar1.Maximum = 50;
            for (int i = 0; i < 50; i++)
            {
                Thread.Sleep(500);
                progressBar1.Value = i + 1;
            }
        }

        private void endButton_Click(object sender, EventArgs e)
        {
            if (myThread != null)
            {
                myThread.Abort(); myThread = null;
                progressBar1.Value = 0;
            }

        }

由于设置了CheckForIllegalCrossThreadCalls = false; 所以,跨线程的操作被允许了,如果没有设置,默认是不允许的,这样会导致抛出异常。

2.使用上下文环境进行操作

这样的操作方式,估计对于问这个问题的人来说,应该是最容易理解的。毕竟他本身是一个Androider。。。Handler类。。。当时受启发,想着我可以利用sendmessage或者postmessage的,但是。。。。不知道为什么sendmessage只接受到了第一次的消息。。。然后,还是使用C#自身的东西吧,避免与操作系统挂钩

具体代码如下:

 

//上下文环境
        SynchronizationContext sc;
        private void nStartButton_Click(object sender, EventArgs e)
        {
            progressBar1.Maximum = 50;
            myThread = new Thread(notCanCrossThreadCall);
            myThread.Start(50);
            //获取上下文环境
            sc = SynchronizationContext.Current;
        }

        private void notCanCrossThreadCall(object m)
        {
            int max = (int)m;
            for (int i = 0; i < max; i++) 
            {
                Thread.Sleep(500);
                //通过上下文环境发送消息,从而设置进度条
                sc.Post(setValues, i + 1);
            }
        }
        private void setValues(object i)
        {
            progressBar1.Value = (int)i;
            if ((int)i == progressBar1.Maximum)
                MessageBox.Show("操作完成");
        }

        private void nEndButton_Click(object sender, EventArgs e)
        {
            if (myThread != null) 
            {
                myThread.Abort();
                myThread = null;
                progressBar1.Value = 0;
            }
        }

3.使用基于事件的异步操作(BackGroundWorker组件)

当时查资料的时候,说到这个的最多。然后,也自然的最近看了下一些人写的异步操作的,从1-4都看完了~不得不说,文章还是可以的。。。

向form中拖一个BackGroundWorker组件,然后,设置对应的三个事件:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgWorker = sender as BackgroundWorker;
            for (int i = 0; i < 50; i++)
            {
                if (bgWorker.CancellationPending) { e.Cancel = true; break; }
                Thread.Sleep(500);
                bgWorker.ReportProgress(i+1);
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.progressBar1.Value = e.ProgressPercentage;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                MessageBox.Show("操作被取消");
            }
            else
            {
                MessageBox.Show("操作完成");
            }
        }

然后,再设置开始和结束按钮的事件:

private void bgStartButton_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.IsBusy != true)
            {
                progressBar1.Maximum = 50;
                backgroundWorker1.WorkerReportsProgress = true;
                backgroundWorker1.WorkerSupportsCancellation = true;
                backgroundWorker1.RunWorkerAsync();
            }
            else 
            {
                MessageBox.Show("busy");
            }
        }
        private void bgEndButton_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.IsBusy && backgroundWorker1.WorkerSupportsCancellation == true)
                backgroundWorker1.CancelAsync();
        }

这里,当RunWorkerAsync被调用,就会产生Do_Work事件,而Do_Work事件中,可以根据需要调用ProgressChanged事件,从而达到进度报告的关系。在异步进程结束的时候,会触发RunWorkerCompleted事件。

***********************************分割线***********************************

以上三种,均可以完成跨线程的操作,不会导致UI界面的假死状态。

其实,第二种可以使用异步中的APM来操作,使用委托来完成。

这样一路搞下来,尤其是在看了第三种的源码,以及第二种使用委托进行异步的原理,感觉上,最基本的就是线程,委托等操作,然后,第三种有种第二种封装的结果。从而给开发者方便,可以高效快速的完成开发工作。

而且,跨线程的操作UI,对于第一种不予评论,是实实在在的跨线程了。

而对于后2种,都是将修改的操作,交给了UI线程去做,通过委托,事件去完成。都是向UI线程发送消息,然后UI线程去修改界面。

标签