【已解决】C#中,将耗时的操作移至独立的线程,以提高UI界面相应速度

【问题】

一个C#的Winform程序,功能已经实现了。

但是存在一个问题:

对于,程序正在运行中,其内部会涉及到网络的操作,比如下载页面等等,

然后运行中的程序,对于界面的事件,比如点击一个按钮,响应很慢,极其的慢,甚至会丢失,无法响应:

harldy to capture when searching

现在希望:

当程序运行中,内部正在进行访问网络等的耗时操作的时候,对于界面中的按钮点击等事件,也可以顺畅的响应,并可以及时处理。

比如停止正在运行中的搜索。

【解决过程】

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的响应,就很流程,不会像之前卡顿,无响应了。



发表评论

电子邮件地址不会被公开。 必填项已用*标注

无觅相关文章插件,快速提升流量