多线程多次查询数据生成一个文件——解决内存溢出

1,需求背景:

       内存溢出的问题最初调试时,发现是生成Excel时用StringBuilder变量缓存数据,当数据过大时,导致内存溢出(解决);之后再次测试,发现由于取数据时涉及到多表查询,之前的逻辑是从各表依次取出数据,都中和至一个DataSet中,然后生成文件,发现在中和的过程中,也可能会发生内存溢出,所以修改为每查寻一个表,往文件中写入一次数据(解决);再次测试,发现当单个表中数据过大时(20W行*265列),查询的时候也会直接发生内存溢出(在服务器上已经测试)。最终经过与XX和XX协商之后,决定采用每次取一定量的数据(可能是1W、2W、5W,以测试速度最快的为准),涉及到跨多个表,多次取数据多次写数据的方式来测底解决内存溢出问题。经过最后结果实际验证,此方法不会出现内存溢出问题。

          考虑到下载历史数据几乎遍布我们每一个项目,但是之前的处理方式,几乎全部都是一次查询,一次生成文件,这样迟早要导致像上述几种问题,最终内存溢出,而且对于大数据量,采用多线程分批导出、分批写数据的方式,也可以在一定程度上缩短时间,所以将该Bug的解决过程与大家分享。

2,错误现象

3,详细设计实现过程

1>.设计思路图

 

上图是解决内存溢出问题的具体设计思路。可以看到其中主要有两块逻辑,代表了两个线程,左边的线程专门用于分页提取数据并加载至缓存,右边的线程专门用于从缓存提取数据写入文件中。当两个线程都运行完毕时,代表文件生成完毕。然后再进行压缩,将生成的压缩文件的路径返回给用户进行下载。

2>. 难点分析

1,线程阻塞与文件生成完毕

由于查询数据线程和写入文件线程是同时在运行的,生成一个文件可能需要多次读写,当写入线程去缓存取数据时,此时缓存里面可能有数据可能没有数据,这都不能做为线程结束的标志,那么究竟什么时候才能判断文件已经生成完毕呢?

只有当查询线程结束并且缓存里面没有数据时,才能知道该文件需要的全部数据已经写入了该文件中,才能断定文件生成完毕,然后终止两个线程进行下一步操作。

2,在顺序的多表中分页读取数据

虽然该功能过程比较繁琐,但因为该功能在分页查询历史数据时已经完成,再次可以直接拿来调用即可。在最终完成该功能之后的测试中发现这块功能还是不够准确,所以进行了小范围的修改,如果大家有兴趣可以去研究一下,有兴趣给我留言求源码!

3>. 核心源代码

1,开启多线程

    首先开启查询数据线程和写入数据线程,并在开启两个线程之后进行阻塞,一直等文件生成完毕才能解除阻塞继续向下执行压缩等步骤。源代码如下:

1.     /// <summary>

2.     /// 生成Excel文件逻辑结构

3.     /// </summary>

4.     ///<returns></returns>

5.     publicstring HistoryExcelDataLogic()

6.     {

7.         //查询数据线程启动

8.         vardueryDataThread = new Thread(QueryDataEvent)

9.                                   {IsBackground =true, Priority =ThreadPriority.Highest, Name = "QueryData"};

10.     dueryDataThread.Start();

11.     //写入数据至文件线程启动

12.     varwriteDataThread = new Thread(WriteDataEvent)

13.                               {IsBackground=true, Priority = ThreadPriority.Highest, Name= "WriteData"};

14.     writeDataThread.Start();

15.     manual.WaitOne();//阻塞线程

16.     //压缩文件,返回路径

17.     returnstring.IsNullOrEmpty(fullpath)? "" :newPrintExcelStreamWrite().RarFile(fullpath);

18. }

2,查询数据至缓存

循环查询出数据加载到缓存队列,用于写入文件。源代码如下:

19. /// <summary>

20. /// 查询数据至缓存

21. /// </summary>

22. void QueryDataEvent()

23. {

24.       ………………..

25.         //查询到在开始时间和结束时间之间的表集合

26.         var tables =TableAllocationQueue.Create().GetTableAllocations(dbseq, condition.BeginTime,condition.EndlessTime);

27.         if (tables ==null)   thrownewException("该终端的起始时间内没有有效的数据表");

28.         //记录下每个表中的有效数据的行数

29.         var listcount = newList<int>();

30.         //组合查询每个表中数据行数SQL语句

31.         var cmdQueryRowCountString =QueryRowCountString(condition);

32.         foreach (TableAllocationEntity entityin tables)

33.         {

34.             ………………..//查询每个表中数据行数

35.            if (ds !=null)listcount.Add(int.Parse(ds.Tables[0].Rows[0]["COUNT"].ToString()));

36.         }

37.         //得到有效数据的总行数,用于分页

38.         int  rows =listcount.Sum();

39.         if (rows <= 0)   thrownewException("该终端的起始时间内有效数据的行数为0");

40.         //如果查询到数据才生成文件

41.         fullpath= newPrintExcelStreamWrite(condition.Vin).CreateFullPath();

42.         int  times;

43.         //根据总的数据行数,得出需要多少次才能取完数据

44.         int.TryParse((rows / PageSize + 1).ToString(),out times);

45.         if (times > 0)

46.         {

47.             //每次取数据的行数

48.             condition.PageSize= PageSize;

49.             //开始循环取数据

50.             for (int i = 1; i<= times; i++)

51.             {

52.                 //表明为分页的页数

53.                 condition.PageIndex= i;

54.                 //分页查询数据

55.                 vards = GetPageData(listcount, dbseq, condition, tables);

56.                 if (ds !=null&& ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)

57.                 {

58.                     lock (queue)

59.                     {

60.                         queue.Enqueue(ds);//将数据放入队列中

61.                     }

62.                 }

63.             }

64.         }

65.     }

66.     ………………..

67.     finally

68.     {

69.         queryFlag= true; //标识查询线程结束

70.     }

71. }

3, 从缓存取数写入文件

循环从缓存队列依次取出缓存数据写入文件,当查询结束并且缓存中没有数据时,表明数据已全部写入文件中,生成文件过程结束,此时需要结束线程,解除阻塞。源代码如下图:

72. /// <summary>

73. ///从缓存取数写入文件

74. ///<summary>

75. void WriteDataEvent()

76. {

77.   //通过查询条件获得需要查询的数据列,并通过MD5值解析出对应的通道名称

78.   ………………..

79.     bool flag =true;

80.     //开始循环取数

81.     while (flag)

82.     {

83.          ………………..

84.             begintime= DateTime.Now;

85.             DataSetds;

86.             //如果缓存中有数据,循环取出队列头上的数据并写入文件中

87.             while (queue !=null&& queue.Count > 0)

88.             {

89.                 lock (queue)

90.                 {

91.                     ds= queue.Dequeue();

92.                 }

93.                 newPrintExcelStreamWrite(condition.Vin).CreateExcel(fullpath, ds, columnLst);

94.             }

95.             //当查询线程结束并且缓存队列中没有数据,表明文件已生成完毕

96.             if (queryFlag && (queue ==null || queue.Count <= 0))

97.             {

98.                 manual.Set();//解除阻塞

99.                 flag= false;//停止循环取数,结束该线程

100.                    }

101.                  …………………………….

102.            }

103.        }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值