【问题】
一个C#的Winform程序,功能已经实现了。
但是存在一个问题:
对于,程序正在运行中,其内部会涉及到网络的操作,比如下载页面等等,
然后运行中的程序,对于界面的事件,比如点击一个按钮,响应很慢,极其的慢,甚至会丢失,无法响应:
现在希望:
当程序运行中,内部正在进行访问网络等的耗时操作的时候,对于界面中的按钮点击等事件,也可以顺畅的响应,并可以及时处理。
比如停止正在运行中的搜索。
【解决过程】
1.其实关于这方面的内容,之前就折腾过:
【已解决】给C#程序添加滚动进度条(ProgressBar),实现滚动/动态更新
所以后来是添加了对应的:
System.Windows.Forms.Application.DoEvents();
去缓解UI无响应的问题,此时的情况就是,已经加了上述代码了,但是结果还是响应很慢。
2。参考:
WebRequest hangs user interface
知道了,本身Winform的UI线程,就是有个大loop循环,不停更新UI,显示UI。
然后此线程中,理当,不应该去执行,比较耗时的网络处理的,
此点,就像android程序一样,也是类似的逻辑。
所以,需要用到C#中的其他机制,比如
BackgroundWorker
去处理,将耗时的网络处理,放到BackgroundWorker中去。
同时,也去参考:
BackgroundWorker Threads and Supporting Cancel
去写自己的代码。
3.现在就去修改,针对本来很耗时的:
string searchResultHtml = crifanLib.getUrlRespHtml(fiverSearchUrl);
操作。
但是,参考过程中,发现,其示例代码,都是把耗时的操作,弄成for循环的,能在每次循环中,去做自己需要做的,耗时的,事情。
然后也会判断,是否被cancel掉了。
然后发现被cancel了,设置cancel,然后另外的bw_RunWorkerCompleted中才有机会去判断e.Cancelled,然后可以及时取消的。
但是,我此处的,耗时的操作,没法被分解为,for循环的那种,所以没有机会去在循环中判断是否被取消,所以无法实现所需要的取消任务的效果。
不过,先不管了,先试试下面的代码:
//string searchResultHtml = crifanLib.getUrlRespHtml(fiverSearchUrl); getUrlRespHtml_bw(fiverSearchUrl); string searchResultHtml = curRespHtml;
以及
private void getUrlRespHtml_bw(string url) { // Create a background thread BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += new DoWorkEventHandler(bw_DoWork); //bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); //init curUrl = url; // run in another thread bw.RunWorkerAsync(); } private void bw_DoWork(object sender, DoWorkEventArgs e) { curRespHtml = crifanLib.getUrlRespHtml(curUrl); } //private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) //{ // // The background process is complete. First we should hide the // // modal Progress Form to unlock the UI. Then we need to inspect our // // response to see if an error occurred, a cancel was requested or // // if we completed successfully. // // Check to see if an error occurred in the background process. // if (e.Error != null) // { // MessageBox.Show(e.Error.Message); // return; // } // // Check to see if the background process was cancelled. // if (e.Cancelled) // { // MessageBox.Show("Processing cancelled."); // return; // } // // Everything completed normally. // // process the response using e.Result // MessageBox.Show("Processing is complete."); //}
看看效果。
结果貌似是可以执行,但是结果还是UI相应极其的慢。没有达到对应的效果:让UI反应很顺畅。
4.经过折腾,目前如下代码:
调用部分:
//string searchResultHtml = crifanLib.getUrlRespHtml(fiverSearchUrl); string searchResultHtml = ""; getUrlRespHtml_bw(fiverSearchUrl); while (bWorkNotCompleted) { System.Windows.Forms.Application.DoEvents(); } searchResultHtml = curRespHtml;
核心部分:
private void getUrlRespHtml_bw(string url) { // Create a background thread BackgroundWorker m_bgWorker = new BackgroundWorker(); m_bgWorker.DoWork += new DoWorkEventHandler(m_bgWorker_DoWork); m_bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler ( m_bgWorker_RunWorkerCompleted ); //init bWorkNotCompleted = true; // run in another thread m_bgWorker.RunWorkerAsync(url); } private void m_bgWorker_DoWork(object sender, DoWorkEventArgs e) { string url = (string)e.Argument; e.Result = crifanLib.getUrlRespHtml(url); } void m_bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { bWorkNotCompleted = true; } private void m_bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // The background process is complete. We need to inspect // our response to see if an error occurred, a cancel was // requested or if we completed successfully. // Check to see if an error occurred in the // background process. if (e.Error != null) { //MessageBox.Show(e.Error.Message); return; } // Check to see if the background process was cancelled. if (e.Cancelled) { //MessageBox.Show("Cancelled ..."); } else { bWorkNotCompleted = false; // Everything completed normally. // process the response using e.Result //MessageBox.Show("Completed..."); curRespHtml = e.Result.ToString(); } }
算是基本工作的,算是,UI响应的速度,有了很大改观。
但是还是不是那种,UI响应,任何时刻,都是十分流畅的。
所以还是没有实现最终目的。
暂时只折腾到这里,有空继续再优化和完善。
5.经过后来的调试,发现,其实是由于自己,另外还有一处,访问网络的代码,没有调用BackgroundWorker,而导致了,之前只是部分提高了UI响应速度。
然后也去把对应的:
string gitHtml = crifanLib.getUrlRespHtml(gigUrl);
改为:
//string gitHtml = crifanLib.getUrlRespHtml(gigUrl); string gitHtml = ""; getUrlRespHtml_bw(gigUrl); while (bWorkNotCompleted) { System.Windows.Forms.Application.DoEvents(); } gitHtml = curRespHtml;
然后,就可以正常实现所需要的:
任何时刻,包括BackgroundWorker在背后去访问网络,UI的响应速度,都和没有执行网络访问,效果是一样的流畅的。
【总结】
将所有的,耗时操作,比如我此处,代码中的两处的网络访问的部分的代码,都改用BackgroundWorker去实现,
由此,即可实现:不论程序是否在(利用BackgroundWorker在背后去)执行耗时的操作,整体的UI的响应速度,都十分流畅。
具体实现代码:
- 原先的是:
string searchResultHtml = crifanLib.getUrlRespHtml(curSearchInfo.searchUrl);
- 改为通过BackgroundWorker的实现耗时操作:
/* 1. related global variables */ private bool bWorkNotCompleted = true; private string curRespHtml = ""; /* 2. BackgroundWorker implementation */ private void getUrlRespHtml_bw(string url) { // Create a background thread BackgroundWorker m_bgWorker = new BackgroundWorker(); m_bgWorker.DoWork += new DoWorkEventHandler(m_bgWorker_DoWork); m_bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler ( m_bgWorker_RunWorkerCompleted ); //init bWorkNotCompleted = true; // run in another thread m_bgWorker.RunWorkerAsync(url); /* pass parameter */ } private void m_bgWorker_DoWork(object sender, DoWorkEventArgs e) { string url = (string)e.Argument; /* got input parameter */ /* move time-consuming work into this xxx_DoWork */ e.Result = crifanLib.getUrlRespHtml(url); } void m_bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { bWorkNotCompleted = true; } private void m_bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // The background process is complete. We need to inspect // our response to see if an error occurred, a cancel was // requested or if we completed successfully. // Check to see if an error occurred in the // background process. if (e.Error != null) { //MessageBox.Show(e.Error.Message); return; } // Check to see if the background process was cancelled. if (e.Cancelled) { //MessageBox.Show("Cancelled ..."); } else { bWorkNotCompleted = false; // Everything completed normally. // process the response using e.Result //MessageBox.Show("Completed..."); curRespHtml = e.Result.ToString(); } } /* call BackgroundWorker version function */ //string searchResultHtml = crifanLib.getUrlRespHtml(curSearchInfo.searchUrl); getUrlRespHtml_bw(curSearchInfo.searchUrl); while (bWorkNotCompleted) { /* allow UI update */ System.Windows.Forms.Application.DoEvents(); } string searchResultHtml = curRespHtml;
由此,即可实现,将耗时的部分,都移至到了BackgroundWorker里面去,
然后使得UI的响应,就很流程,不会像之前卡顿,无响应了。