C#多執行緒與UI響應

iDotNetSpace發表於2009-07-16

一.            概述

在使用C#進行應用程式設計時,經常會採用多執行緒的方式進行一些後臺任務的工作。對於不同的應用場景,使用的策略也不盡相同。

1.      後臺迴圈任務,少量UI更新:例如批量上傳檔案,並提供進度。這種情況使用BackgroundWorker元件是非常好的選擇。

2.      耗時的後臺任務:這裡的耗時任務是指一個時間較長的任務,並且不能精確獲取進度,如:呼叫一個遠端WebService介面。這種情況可以開兩個執行緒,一個工作,一個更新UI(不能提供進度,只能顯示動畫表示系統在執行中)。

3.      耗時的UI任務:當工作壓力集中在UI響應上時,可以在工作者執行緒中增加延時,從而讓UI執行緒獲得響應時間。整個工作的總體時間會增加,但使用者響應效果會好很多。

 

二.            後臺的迴圈任務,少量UI更新

這種情況使用BackgroundWorker元件是最好的選擇。(詳見附一)

 

三.            後臺耗時任務

在後臺執行一個不可分解的耗時任務,需要進行介面更新,以便讓客戶看上去程式有所響應。這種情況下,UI執行緒一般也不知道工作執行緒何時結束,所以一般執行迴圈任務,當工作執行緒結束後,關閉UI執行緒就可以了。

Thread uithread = null;

        private void btnStart_Click(object sender, EventArgs e)

        {

            uithread = new Thread(new ThreadStart(this.UpdateProgressThread));

            uithread.Start();

 

            Thread workthread = new Thread(new ThreadStart(this.DoSomething));

            workthread.Start();

        }

 

        private void DoSomething()

        {

            Thread.Sleep(5000);

             uithread.Abort();

             MessageBox.Show("work end");

        }

 

        private void UpdateProgressThread()

        {

            for (int i = 0; i < 10000; i++)

            {

                Thread.Sleep(100); 

                this.Invoke(new Action<int>(this.UpdateProgress), i);

            }

        }

 

        private void UpdateProgress(int v)

        {          

            this.progressBar1.Value = v;

    }

 

這裡只要注意一點:執行緒呼叫的方法都不能訪問使用者控制元件,必須通過委託呼叫Form的方法來實現介面更新。

 

四.            耗時的UI任務

當整個工作壓力集中在UI響應上時,可以在工作者執行緒中增加延時,從而讓UI執行緒獲得響應時間。整個工作的總體時間會增加,但使用者響應效果會好很多。

private void FormInitForm_Load(object sender, EventArgs e)

        {

            this.listView1.Items.Clear();

            Thread workthread = new Thread(new ThreadStart(this.DoSomething));

            workthread.Start();

        }

 

        private void DoSomething()

        {

            for (int i = 0; i < 30; i++)

            {

                this.Invoke(new Action<int>(this.LoadPicture), i);

                Thread.Sleep(100);

            }

        }

 

        private void LoadPicture(int i)

        {

            string text = string.Format("Item{0}", i);

            ListViewItem lvi = new ListViewItem(text, 0);

            this.listView1.Items.Add(lvi);

            Thread.Sleep(200);//模擬耗時UI任務,非迴圈,不可分解

        }

 

五.            補充

1.        Invoke  BeginInvoke

在多執行緒程式設計中,我們經常要在工作執行緒中去更新介面顯示,而在多執行緒中直接呼叫介面控制元件的方法是錯誤的做法,正確的做法是將工作執行緒中涉及更新介面的程式碼封裝為一個方法,通過 Invoke 或者 BeginInvoke 去呼叫,兩者的區別就是一個導致工作執行緒等待,而另外一個則不會。

而所謂的“一面響應操作,一面新增節點”永遠只能是相對的,使 UI 執行緒的負擔不至於太大而以,因為介面的正確更新始終要通過 UI 執行緒去做,我們要做的事情是在工作執行緒中包攬大部分的運算,而將對純粹的介面更新放到 UI 執行緒中去做,這樣也就達到了減輕 UI 執行緒負擔的目的了。

 

2.        Application.DoEvent

在耗時的迴圈的UI更新的方法中,插入Application.DoEvent,會使介面獲得響應,Application.DoEvent會呼叫訊息處理程式。

private void button2_Click(object sender, EventArgs e)

        {

            for (int i = 0; i < 30; i++)

            {

                string text = string.Format("Item{0}", i);

                ListViewItem lvi = new ListViewItem(text, 0);

                this.listView1.Items.Add(lvi);

                Thread.Sleep(200);

                for (int j = 0; j < 10; j++)

                {

                    Thread.Sleep(10);

                    Application.DoEvents();

                }

            }           

        }

3.        Lock

lock(object)

{

}

等價與

try

            {

                Monitor.Enter(object);

            }

            finally

            {

                Monitor.Exit(object)

       }

 

附一:

BackgroundWorker元件使用說明

一.            概述

BackgroundWorker是·NET 2.0提供的一個多執行緒元件,在應用程式中使用,可以非常簡單方便地實現UI控制元件通訊,並自動處理多執行緒衝突問題。

 

二.            基本屬性

1.      WorkerReportsProgress bool:是否允許報告進度;

2.      WorkerSupportsCancellationbool:是否允許取消執行緒。

3.      CancellationPendingboolget:讀取使用者是否取消該執行緒。

 

三.            基本事件

1.      DoWork:工作者執行緒

2.      RunWorkerCompleted :執行緒進度報告

3.      ProgressChanged:執行緒結束報告

 

四.            基本方法

1.      RunWorkerAsync() :啟動工作者執行緒;

2.      CancelAsync():取消工作者執行緒;

3.      ReportProgress(int);   報告進度

 

五.            程式碼

//啟動

private void btnStart_Click(object sender, EventArgs e)

{

    this.btnStart.Enabled = false;

    this.btnStop.Enabled = true;

 

    this.backgroundWorker.RunWorkerAsync();

}

 

//通知執行緒停止

private void btnStop_Click(object sender, EventArgs e)

{

    this.backgroundWorker.CancelAsync();

}

 

//工作者執行緒

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)

{

    for (int i = 0; i < 150; i++)

    {

        if (backgroundWorker.CancellationPending)   //檢視使用者是否取消該執行緒

        {

            break;

        }

 

        System.Threading.Thread.Sleep(50);          //乾點實際的事

 

        backgroundWorker.ReportProgress(i);         //報告進度

    }

}

 

//執行緒進度報告

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)

{

    this.progressBar1.Value = e.ProgressPercentage * 100 / 150;

}

 

//執行緒結束報告

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

{           

    this.btnStart.Enabled = true;

    this.btnStop.Enabled = false;

}    

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-609330/,如需轉載,請註明出處,否則將追究法律責任。

相關文章