前些天,有个朋友让我给他演示下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线程去修改界面。
P.S.
人生的第一次实习生笔试华丽丽的没了。。。。祝好运吧