在ASP.Net MVC中长时间运行SQL查询的最佳实践

前端之家收集整理的这篇文章主要介绍了在ASP.Net MVC中长时间运行SQL查询的最佳实践前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个动作方法,需要根据用户选择的日期完成15~52个长时间运行的SQL查询(所有这些查询都相似,每个查询需要5秒以上才能完成).

在做了大量研究之后,似乎在不阻塞ASP.Net线程的情况下执行此操作的最佳方法是使用async / await任务方法使用SQL查询

  1. [HttpPost]
  2. public async Task<JsonResult> Action() {
  3. // initialization stuff
  4.  
  5. // create tasks to run async sql queries
  6. ConcurrentBag<Tuple<DateTime,List<long>>> weeklyObsIdBag =
  7. new ConcurrentBag<Tuple<DateTime,List<long>>>();
  8. Task[] taskList = new Task[reportDates.Count()];
  9. int idx = 0;
  10. foreach (var reportDate in reportDates) { //15 <= reportDates.Count() <= 52
  11. var task = Task.Run(async () => {
  12. using (var sioDbContext = new SioDbContext()) {
  13. var historyEntryQueryable = sioDbContext.HistoryEntries
  14. .AsNoTracking()
  15. .AsQueryable<HistoryEntry>();
  16. var obsIdList = await getObsIdListAsync(
  17. historyEntryQueryable,reportDate
  18. );
  19. weeklyObsIdBag.Add(new Tuple<DateTime,List<long>>(reportDate,obsIdList));
  20. }
  21. });
  22. taskList[idx++] = task;
  23. }
  24. //await for all the tasks to complete
  25. await Task.WhenAll(taskList);
  26.  
  27. // consume the results from long running sql queries,// which is stored in weeklyObsIdBag
  28. }
  29.  
  30. private async Task<List<long>> getObsIdListAsync(
  31. IQueryable<HistoryEntry> historyEntryQueryable,DateTime reportDate
  32. ) {
  33. //apply reportDate condition to historyEntryQueryable
  34.  
  35. //run async query
  36. List<long> obsIdList = await historyEntryQueryable.Select(he => he.ObjectId)
  37. .Distinct()
  38. .ToListAsync()
  39. .ConfigureAwait(false);
  40. return obsIdList;
  41. }

进行此更改后,完成此操作所需的时间大大减少,因为现在我可以同时执行多个(15~52)异步SQL查询并等待它们完成而不是按顺序运行它们.但是,用户开始遇到很多超时问题,例如:

  1. (from Elmah error log)
  2. "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool.
  3. This may have occurred because all pooled connections were in use and max pool size was
  4. reached."
  5. "The wait operation timed out"

它是由线程饥饿引起的吗?我感觉我可能在线程池中使用了太多线程来实现我想要的东西,但我认为这应该不是问题,因为我使用async / await来阻止所有线程被阻塞.

如果事情不会以这种方式工作,那么执行多个长时间运行的SQL查询的最佳做法是什么?

解决方法

考虑限制正在执行的并发任务的数量,例如:
  1. int concurrentTasksLimit = 5;
  2. List<Task> taskList = new List<Task>();
  3. foreach (var reportDate in reportDates) { //15 <= reportDates.Count() <= 52
  4. var task = Task.Run(async () => {
  5. using (var sioDbContext = new SioDbContext()) {
  6. var historyEntryQueryable = sioDbContext.HistoryEntries
  7. .AsNoTracking()
  8. .AsQueryable<HistoryEntry>();
  9. var obsIdList = await getObsIdListAsync(
  10. historyEntryQueryable,reportDate
  11. );
  12. weeklyObsIdBag.Add(new Tuple<DateTime,obsIdList));
  13. }
  14. });
  15. taskList.Add(task);
  16. if (concurrentTasksLimit == taskList.Count)
  17. {
  18. await Task.WhenAll(taskList);
  19. // before clearing the list,you should get the results and store in memory (e.g another list) for later usage...
  20. taskList.Clear();
  21. }
  22. }
  23. //await for all the remaining tasks to complete
  24. if (taskList.Any())
  25. await Task.WhenAll(taskList);

请注意,我将taskList更改为实际的List< Task>,它似乎更容易使用它,因为我们需要从列表中添加/删除任务.

此外,您应该在清除taskList之前获取结果,因为您将在以后使用它们.

猜你在找的asp.Net相关文章