一.            概述

在使用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.      WorkerSupportsCancellation bool :是否允许取消线程。

3.      CancellationPending boolget :读取用户是否取消该线程。

 

三.            基本事件

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 ;

} 

posted on 2009-07-16 23:08  宋元  阅读(204)  评论(0编辑  收藏  举报