封装多线程处理大量数据操作(一)
起因:
最近在写一个导数据的程序,需要从几个老数据表中取出n多的数据,然后加以处理再添加到新数据库的对应表中。单步操作太慢了,这不正是多线程的用武之地吗?
对于每一种数据我都得写一套类似的代码,表意代码如下
//从老数据库中获得一批老数据 DataSet dsUser = OldDbAccess.GetOldUsers(minId); //将dataset中的数据分成n份,放到sectionUsers的datatable中 DataTable[] sectionUsers = new DataTable[threadCn]; //声明threadCn个AutoResetEvent,让他们在线程执行完毕后发出执行完毕的信号量 AutoResetEvent[] evts = new AutoResetEvent[threadCn]; //初始化evts的值 //将数据和AutoResetEvent放到一个数据中交给ThreadPool去处理,具体的处理方法略去了
由于老数据有n种,每一种的处理方法又不一样,所以我写了n个类似上面的处理步骤,这太累了吧,于是想重构,将上面的操作步骤中相同的地方提取出来。于是有了AsyncHelper静态类,这个静态类有几个公开的静态方法来做上面那些分多个线程处理大量数据的步骤中一样的过程。
第一个方法的签名应该是这样子的
/// <summary> /// 执行多线程操作任务 /// </summary> /// <param name="dataCollection">多线程操作的数据集合</param> /// <param name="threadCn">分多少个线程来做</param> /// <param name="processItemMethod">处理数据集合中单个数据使用的处理方法</param> public static void DoAsync(IList dataCollection, int threadCn, WaitCallback processItemMethod)
大量的数据定义一个IList类型的类作为参数传递,要分多少个线程来处理这些数据用threadCn指定,需要注意的是WaitHandle.WaitAll方法决定了threadCn必须小于64,最后一个参数是处理大量数据中的一个数据的方法。方法的签名出来了,我在上面又做了几次重复的处理,写这个方法应该不是个问题。同样的我们也是需要把IList数据分成threadCn份,然后将每一份都交给ThreadPool来处理,这很简单。
/// <summary> /// 执行多线程操作任务 /// </summary> /// <param name="dataCollection">多线程操作的数据集合</param> /// <param name="threadCn">分多少个线程来做</param> /// <param name="processItemMethod">处理数据集合中单个数据使用的处理方法</param> public static void DoAsync(IList dataCollection, int threadCn, WaitCallback processItemMethod) { if (dataCollection == null) throw new ArgumentNullException("dataCollection"); if (threadCn >= 64 || threadCn < 2) { throw new ArgumentOutOfRangeException("threadCn", "threadCn 参数必须在2和64之间"); } if (threadCn > dataCollection.Count) threadCn = dataCollection.Count; IList[] colls = new ArrayList[threadCn]; AutoResetEvent[] evts = new AutoResetEvent[threadCn]; for (int i = 0; i < threadCn; i++) { colls[i] = new ArrayList(); evts[i] = new AutoResetEvent(false); } for (int i = 0; i < dataCollection.Count; i++) { object obj = dataCollection[i]; int threadIndex = i % threadCn; colls[threadIndex].Add(obj); } for (int i = 0; i < threadCn; i++) { ThreadPool.QueueUserWorkItem(DoPrivate, new object[] { colls[i],processItemMethod,evts[i] }); } WaitHandle.WaitAll(evts); } private static void DoPrivate(object data) { object[] datas = data as object[]; IList dataList = datas[0] as IList; WaitCallback method = datas[1]; AutoResetEvent evt = datas[2] as AutoResetEvent; foreach (object item in dataList) { method(item); } evt.Set(); }
这个很容易实现,不过既然要做封装我们就不得不多考虑一些,我们的线程好比是一个一个的侦查兵,这次给他们的任务是抓一个敌人回来问问敌情,任务要求只抓一个敌人,也就是说如果某一个侦查兵抓到一个敌人之后要给其他战友发信息,告诉他们别忙了,任务已经完成了。这个该怎么办呢,办法总是要比问题多的。
WaitHandle类有WaitAny静态方法,上面侦察兵的例子不就是个WaitAny吗,主线程需要在接受到一个线程完成的信号后通知所有线程,“任务完成了,大家都回家吧”?大家如果有兴趣的话,可以给出自己的方案,我的方案明天放出来。明天一并要解决的还有取得多个执行操作的返回值问题。