需求背景:
统计作业,即每天对前一天所有的数据进行统计。由于统计的结果十分重要,所以此功能必须能力求稳定,采用异步委托的方式来遍历计算统计数据,这样可以同时统计多个终端的数据,可以缩短整体的计算时间。
1>.设计思路图
1.1统计所有终端数据流程图
启动服务之时,会获取所有终端至缓存,取出这些终端,在依次从数据库取出该终端的最后计算时间,没有最后计算时间的表示还没有计算过,就取出终端上传的最早的数据的时间,以备从该时间点开始统计终端到目前为止每天的里程数据,终端的状态全部被设置为‘未运行’,然后遍历该集合,依次对各终端的里程数据进行统计,此处使用了异步委托,当一个计算开始时,程序不需要等该计算完毕就可以开始计算下一个终端,在此处使用信号量管理异步委托计算的个数,设置为5个,即同一时间最多只能让5个终端同时进行计算,当有5个计算在进行时,程序会处于阻塞状态,当异步委托的回调函数开始运行时,表明该计算已经结束,释放一个信号量用于下一个终端的计算,依次进行计算,直到将该集合全部计算一遍为止。如下图所示:
(1-2)统计所有终端里程流程图
1.2统计终端数据过程详解
统计终端里程开始,即计算开始,先更新终端缓存队列里该终端的状态为‘正在计算’,通过缓存的该终端的最后计算时间是否为昨天判断是否需要进行里程统计,如果已经为昨天,表明里程统计已经进行过了,不需要再进行统计了;如果不为昨天,则需要统计缓存的该终端的最后计算时间的下一天里程数据,直到计算到昨天为止,该终端的里程统计才结束。如下图所示:
(1-3)统计终端里程过程详解图
2>. 核心源代码
1, 异步委托遍历终端集合进行计算
通过信号量来控制可以同时进行的计算个数,每统计完一个,信号量释放一个,就可以统计下一个终端数据。源代码如下:
1. //初始化信号量
2. Semaphore semaphore=newSemaphore(5,5);
3. ///每日作业,判断终端是否需要进行里程统计
4. publicvoid DayDo(int k)
5. {
6. ............
7. //初始化委托
8. varcomputeDelegate =new ComputeDelegate(Compute);
9. //遍历终端对象集合,依次进行计算
10. foreach (var entityinTerminalJobList)
11. {
12. //如果终端的最后统计时间不为昨天,并且该终端的状态不为正在运行,表明该终端任有里程需要统计
13. if (DateTime.Compare(entity.LastJobTime, yesterday)!= 0 && entity.Status != TerminalStatusEnum.Working)
14. {
15. //异步委托开始计算该终端
16. computeDelegate.BeginInvoke(entity,CallBack,null);
17. //信号量减1
18. semaphore.WaitOne();
19. }
20. }
21. ............
22. }
23. /// <summary>
24. /// 计算回调函数,用于释放当前信号
25. /// </summary>
26. /// <paramname="asyncResult"></param>
27. void CallBack(IAsyncResult asyncResult)
28. {
29. ............
30. //回调结束,说明计算完毕,释放该信号,让下一个终端的计算开始
31. semaphore.Release();
32. }
33. /// <summary>
34. ///计算
35. /// </summary>
36. /// <param name="entity"></param>
37. void Compute(TerminalJobEntity entity)
38. {
39. ............
40. //初始化计算条件对象
41. varcondition = new TerminalJobCondition()
42. {
43. TerminalCode= entity.TerminalCode,
44. DbSeq= entity.DbSeq
45. };
46. if (DateTime.Compare(entity.LastJobTime,_minDateTime) == 0)//终端的最后计算时间如果为无效时间,从原始数据的最小时间开始
47. {
48. //取出该终端第一次上传数据的时间
49. entity.LastJobTime= GetHistoryTime(entity.DbSeq, entity.TerminalCode);
50. if (DateTime.Compare(entity.LastJobTime,_minDateTime) == 0)//查询不到该终端第一次上传数据的时间,说明该终端还没上线使用,里程等都没有数据,就从当天开始统计,全部为0
51. {
52. condition.StartTime= DateTime.Today.AddDays(-1);
53. //插入为0的里程数据到数据库
54. InsertMileageData(GetMileage(condition,false));
55. return;
56. }
57. else
58. entity.LastJobTime= DateTime.Parse(entity.LastJobTime.ToShortDateString()).AddDays(-1);//查询到有效时间,设置有效时间的前一天为开始时间,因为下面的计算会加一天,从有效时间当天开始
59. }
60. //开始计算之前设置缓存里该终端的状态为正在进行
61. UpdateStatus(condition.TerminalCode,entity.LastJobTime, 2);
62. bool flag =true;//计算是否成功标识位
63. //当最后统计时间小于昨天时,从最后开始时间一直统计每天里程直到昨天
64. while (DateTime.Compare(entity.LastJobTime,yesterday) < 0 && flag)
65. {
66. //统计的开始时间为上次统计时间的下一天的00:00:00,结束时间为23:59:59
67. condition.StartTime= entity.LastJobTime.AddDays(1);
68. condition.EndlessTime= entity.LastJobTime.AddDays(2);
69. //统计当前时间的里程,并返回统计是否成功的结果
70. flag= QueryMileageData(condition);
71. //更新最后统计时间为已经统计的那天
72. entity.LastJobTime=entity.LastJobTime.AddDays(1);
73. }
74. //更新作业对象缓存的时间和状态
75. UpdateStatus(entity.TerminalCode,entity.LastJobTime, 3);
76. ............
77. }
2, 最大里程和当日里程的详细计算过程
取出统计当天的最大数据和最小数据,以及前一天的最大数据进行计算。源代码如下:
1. ///统计里程
2. bool QueryMileageData(TerminalJobCondition condition)
3. {
4. ............
5. //获取统计开始时间和结束时间数据所在的历史数据表
6. vartables = TableAllocationQueue.Create().GetTableAllocations(condition.DbSeq,condition.StartTime, condition.EndlessTime);
7. //如果不存在表,即没有有效数据,初始数据即为统计结果插入数据库
8. if (tables.Count == 0)
9. return InsertMileageData(GetMileage(condition,true));
10. //组装SQL语句,用于查询开始时间和结束时间范围内的最大里程和最小里程,以及查询小于开始时间的最大里程
11. varsb = new StringBuilder();
12. sb.Append("select * from (select terminalcode,mileage from {0}");
13. sb.Append(
14. string.Format(
15. "where traveltime>=to_date('{0}','yyyy-MM-ddhh24:mi:ss') and traveltime<to_date('{1}','yyyy-MM-dd hh24:mi:ss') andterminalcode={2} and mileage!=0 order by traveltime desc) where rownum=1",
16. condition.StartTime,condition.EndlessTime, condition.TerminalCode));
17. sb.Append(" union all select * from ( select terminalcode,mileagefrom {0}");
18. sb.Append(
19. string.Format("wheretraveltime<to_date('{0}','yyyy-MM-dd hh24:mi:ss') and terminalcode={1} andmileage!=0 order by traveltime desc) where rownum=1",
20. condition.StartTime,condition.TerminalCode));
21. sb.Append(" union all select * from ( select terminalcode,mileagefrom {0}");
22. sb.Append(
23. string.Format(
24. "where traveltime>=to_date('{0}','yyyy-MM-ddhh24:mi:ss') and traveltime<to_date('{1}','yyyy-MM-dd hh24:mi:ss') andterminalcode={2} and mileage!=0 order by traveltime asc) where rownum=1",
25. condition.StartTime,condition.EndlessTime, condition.TerminalCode));
26. //查询数据
27. ............
28. //如果找不到有效数据,初始数据即为统计结果
29. if (ds.Tables.Count == 0 || ds.Tables[0].Rows.Count<= 0)
30. return InsertMileageData(GetMileage(condition,true));
31. //统计出最大里程,当日里程
32. varentity =
33. (fromDataRow rowin
34. ds.Tables[0].AsEnumerable().Where<DataRow>(r=>double.Parse(r["Mileage"].ToString()) != 0)
35. grouprow by row["TerminalCode"]
36. intog
37. selectnew MileageEntity
38. {
39. Seqid= "emp_sequence.nextval",
40. TerminalCode= condition.TerminalCode,
41. AccumulatedMileage= g.Max(p =>double.Parse(p["Mileage"].ToString())),
42. DayOfMileage=
43. g.Max(p=>double.Parse(p["Mileage"].ToString())) -
44. g.Min(p=>double.Parse(p["Mileage"].ToString())),
45. Datetime= condition.StartTime
46. }).ToList();
47. return InsertMileageData(entity.Count == 0 ?GetMileage(condition,true) : entity[0]);
48. ............
49. }