一直从事ERP/MIS开发,总结一下,自己在ERP/MIS开发中,经常用到的应用多线程的两种模式。
先举一个例子,以帮忙回忆起对多线程的印象。CopyFilesProc是实现拷贝文件的一个方法,用多线程调用:
Thread simpleThread = new Thread(CopyFilesProc);
simpleThread.Name = "CopyFiles";
simpleThread.Start();
启动调用,在VS2010中,增加了线程调试窗口,以查看当前进程的线程。我的理解是,总是记得给你的线程命名。
1 需要与界面互动的场景,需要实现报告进度,应用BackgroundWorker组件
如图,月结的界面效果图,当用户点击Process按钮后,出现进度条,显示处理进度
对于BackgroundWorker控件的运用,请查阅MSDN知识库。我这里对报告进度这一小功能,作说明。
报告进度的功能,分两种情况来实现。一种是处理任务(Job)没有用接口实现,当前进度变量存在于窗体中,源码如下
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
bgWorker.ReportProgress(10, fileName);
}
直接在DoWork事件中,调用ReportProgress方法,如果可能,还可以传入变量值
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string fileName = e.UserState.ToString();
}
注册事件ProgressChanged来输出当前正在处理的项目,在这个方法中可以操作UI控件。
另一种方法是,处理任务的实现(Job的功能实现)被隔离到第三方的类库中,说白了就是放到外部的类文件中,不与窗体代码混淆在一起。代码举例,对于月结功能,可能的一个这样的实现
public interface IJob
{
void Execute();
}
实现文件
public class Job : IJob
{
public void Execute()
{
}
}
在窗体的bgWorker_DoWork方法中,调用实现job.Execute()来这现月结功能。
这种方法下,报告进度的功能稍微复杂一点,要用到定时器,进度存放变量值,执行任务时更新进度。
实现步骤如下,在接口实现文件,定义存放进度变量的集合值_JobProcess变量,
public class Job : IJob
{
private static ConcurrentDictionary<int, bool> _JobProcess;
在执行长时间任务前,初始化进度变量
public void Execute()
{
if (_JobProcess == null || _JobProcess.Count== 0||_JobProcess.Count>0)
{
_JobProcess = new ConcurrentDictionary<int, bool>();
for (int i = 1; i <= _Step; i++)
{
_JobProcess.TryAdd(i, false);
}
}
_Step是步长,这里有简化处理。实际操作中,要对任务进行分类处理,以达到分步的目的。比如,核算客户往来帐目,就以客户为依据来分步长,有121个客户,这里_Step就是121,然后每处理完一个客户的帐目,就把它设置为已经处理,即这样来调用
for (int i = 1; i <= _Step; i++)
{
_JobProcess[i] = true;
Thread.Sleep(2000);
}
每处理一个任务,就把它的时度标志为true,以表示已经处理。这样,在进度反馈接口中,可以及时反馈结果
public object[] GetExecuteStatus()
{
//已经处理完成
int completedCount = (from item in _JobProcess
where item.Value == true
select item).Count();
//当前总任务数量
int totalCount = (from item in _JobProcess
select item).Count();
//正在处理的任务
int itemInProcess = (from item in _JobProcess
where item.Value == false
orderby item.Key
select item.Key).FirstOrDefault();
}
将这三个值返回到界面的timer的Tick事件中,实时改变界面的ProgressBar的状态,以达到报告进度的目的。
当完成功能需要一定的时间,一般定为超过20second,都应该用BackgroundWorker来处理,以保持界面及时响应。
2 不需要界面发生互动,后台运行完成后,显示结果即可,应用WorkerThreadBase模式
这个模式最主要的实现类是WorkerThreadBase,
public abstract class WorkerThreadBase : IDisposable
{
private Thread _workerThread;
private ManualResetEvent _stopping;
private ManualResetEvent _stopped;
private bool _disposed;
private bool _disposing;
使用了ManualResetEvent来同步多个线程,MSDN中对ManualResetEvent的解释是:通知一个或多个正在等待的线程已发生事件. 如果不能明白它的意思,就跑一下测试代码,来看看用途
DummyWorker dummyWorker = new DummyWorker();
dummyWorker.Start();
CopyFileWorker copyFileWorker = new CopyFileWorker(_copyInfo);
copyFileWorker.Start();
//wait for the two threads to finish
WorkerThreadBase.WaitAll(copyFileWorker, dummyWorker);
如代码所示,创建2个工作线程,工作线程的创建方法如下
public class DummyWorker:WorkerThreadBase
{
protected override void Work()
{
}
}
继承于WorkerThreadBase类型,重写Work方法即可。
当调用WorkerThreadBase.WaitAll停止当前线程,等待所有的线程运行完毕后,然后可以显示结果。
总结,后一种模式,编程简单,效率也高一些;在WinForm应用中,也常常在Forms类型的方法中,动态创建BackgroundWorker然后调用它,以保持UI继续接受用户输入。
请到epn.codeplex.com中下载源代码。