我们经常碰到这种“响应迟钝”的现象:把光盘放入光驱,在“我的电脑”上双击光盘驱动器,这时,鼠标变成漏斗状了,这过程可能要持续几秒种的时间。如果我们想再打开其它驱动器C:\,这时并不能马上进入C:\,因为UI(用户界面)线程还在执行读取光盘的操作,有时甚至出现“没有响应”的假死现象。当然,我们在桌面应用程序也经常碰到这种情况,使得用户体验变得糟糕,那么怎样才能解决这种情况?这里,我们介绍三种UI界面快速响应的三种方法:
我们的方案是需要执行长时间的任务不在UI线程上面执行,而由其它手动创建的线程或线程池里的线程来执行。这里,我们创建了三个窗体,每个窗体上都有一个Label控件(lblResult)、一个Button(btnWork)控件。
1. 简单的多线程模式
通过手动创建线程(工作线程)来执行任务。这里的DoSomeWorkSlow()并没有封装在类 Worker 里面,因为在类里面访问不到由UI线程创建的窗体控件,而我们又需要把执行结果显示在窗体上。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
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 线程,不容易造成死锁,所以尽可能多用该方法。
而计时器模式是最耗资源的,因为该模式使用了计时器,定时地执行一个方法体,必然增加额外的系统开销。
本实例的源码您可以在这里下载得到:
http://www.04jd.com/doho/download
您也可以参考:
Ian Griffiths--- 通过多线程为基于 .NET 的应用程序实现响应迅速的用户