获得良好UI响应的三种方法

我们经常碰到这种“响应迟钝”的现象:把光盘放入光驱,在“我的电脑”上双击光盘驱动器,这时,鼠标变成漏斗状了,这过程可能要持续几秒种的时间。如果我们想再打开其它驱动器C\,这时并不能马上进入C\,因为UI(用户界面)线程还在执行读取光盘的操作,有时甚至出现“没有响应”的假死现象。当然,我们在桌面应用程序也经常碰到这种情况,使得用户体验变得糟糕,那么怎样才能解决这种情况?这里,我们介绍三种UI界面快速响应的三种方法:

我们的方案是需要执行长时间的任务不在UI线程上面执行,而由其它手动创建的线程或线程池里的线程来执行。这里,我们创建了三个窗体,每个窗体上都有一个Label控件(lblResult)、一个ButtonbtnWork)控件。

1.       简单的多线程模式

通过手动创建线程(工作线程)来执行任务。这里的DoSomeWorkSlow()并没有封装在类 Worker 里面,因为在类里面访问不到由UI线程创建的窗体控件,而我们又需要把执行结果显示在窗体上。

private void btnWork_Click(object sender, EventArgs e)

        {

            lblResult.Text = "执行中…";

            btnWork.Enabled = false;

            trdWorker = new Thread(new ThreadStart(DoSomeWorkSlow));

            trdWorker.Start();

        }

        //执行的任务

        private void DoSomeWorkSlow()

        {

            long tmp;

            for (i = 0; i < 50000; i++)

            {

                tmp = 0;

                for (long j = 0; j < 50000; j++)

                {

                    tmp++;

                }

            }

            //更新窗体界面

            object[] args = { this, System.EventArgs.Empty };

            lblResult.BeginInvoke(new System.EventHandler(UpdateLblResult), args);

        }

 

        private void UpdateLblResult(object o, System.EventArgs e)

        {

            lblResult.Text = i.ToString();

            btnWork.Enabled = true;

   }

2.       异步代理模式

使用代理的BeginInvoke方法,该方法的执行过程是直接调用线程池里的现有空闲线程(若无,则由CLR自动创建)来执行任务,并返回一个带 IAsyncResult 接口的方法AfterWork(IAsyncResult result)

public partial class AsynchronizationDelegate : Form

    {

        private Worker worker;

   private delegate long deleWorker(long para);

...

private void btnWork_Click(object sender, EventArgs e)

        {

            lblResult.Text = "执行中…";

            btnWork.Enabled = false;

            worker = new Worker();

            deleWorker work = new deleWorker(worker.DoSomeWorkSlow);

            work.BeginInvoke(50000, new AsyncCallback(AfterWork), null);

        }

        //执行完worker.DoSomeWorkSlow() 后返回执行AfterWork(IAsyncResult result)

        private void AfterWork(IAsyncResult result)

        {                                     

            object[] args = {this,System.EventArgs.Empty};

            lblResult.BeginInvoke(new System.EventHandler(UpdateLblResult), args);

   }

}

其中Worker 类封装了两个方法:DoSomeWorkSlow(long para)DoSomeWorkSlowNoReturn()

class Worker

    {

        private long result;

        public long Result

        {

            get

            {

                return result;

            }

            set

            {

                result = value;

            }

        }

 

        public long DoSomeWorkSlow(long para)

        {

            long i,tmp;

            for (i = 0; i < para; i++)

            {

                tmp = 0;

                for (long j = 0; j < para; j++)

                {

                    tmp++;

                }

            }

            result = i;

            return i;

        }

 

        public void DoSomeWorkSlowNoReturn()

        {

            long i, tmp;

            for (i = 0; i < 50000; i++)

            {

                tmp = 0;

                for (long j = 0; j < 50000; j++)

                {

                    tmp++;

                }

 

            }

            result = i;

        }

}

3.       计时器回调模式

该模式是通过System.Threading.Timer定时地调用TimerCallback代理指定的void 方法UpdateUI(object state),在该方法里判断IsComplete是否为true来执行相关操作。当然,在这里我们直接判断worker.Result != 0,并没有设置变量IsComplete

public partial class Timer_CallBack : Form

    {

        private Worker worker;

        System.Threading.Timer tmr;

...

private void btnWork_Click(object sender, EventArgs e)

        {

            lblResult.Text = "执行中…";

            btnWork.Enabled = false;

            worker = new Worker();

            Thread trd = new Thread(new ThreadStart(worker.DoSomeWorkSlowNoReturn));

            trd.Start();

            TimerCallback tmrCallback = new TimerCallback(UpdateLblResult);

            tmr = new System.Threading.Timer(tmrCallback, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));

        }

 

        private void UpdateLblResult (object state)

        {

            if(worker.Result != 0)//任务已执行完,得到执行结果

            {

                object[] args = {this,System.EventArgs.Empty};

                lblResult.BeginInvoke(new System.EventHandler(UpdateLblResult),args);

                tmr.Dispose();//消毁计时器

            }

   }

      }

第一种模式是通过手动创建工作线程来执行DoSomeWorkSlow()方法,而不在UI线程上面执行,这样UI线程就可以响应其它事件,界面响应就变得迅速多了(延迟时间最好在 100ms 以内)。执行完该方法以后,通常还需要把结果显示在UI窗体上,这里用到Control类的BeginInvoke方法,该方法是Windows 线程规则的一个“例外”之一,它允许在UI线程之外的任何线程调用UI线程创建的窗体控件。

第二种模式异步代理模式以异步方式在线程池的某个线程上运行,由于BeginInvoke 从不等待 UI 线程,不容易造成死锁,所以尽可能多用该方法。

而计时器模式是最耗资源的,因为该模式使用了计时器,定时地执行一个方法体,必然增加额外的系统开销。

本实例的源码您可以在这里下载得到:

https://files.cnblogs.com/Doho/BetterUIResponse.rar

您也可以参考:

http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/misMultithreading.mspx?mfr=true

Ian Griffiths---通过多线程为基于 .NET 的应用程序实现响应迅速的用户
posted @ 2006-05-24 00:34  Doho  阅读(689)  评论(0编辑  收藏  举报