【发布时间】:2018-02-06 08:39:07
【问题描述】:
我有一个动作方法,它需要根据用户选择的日期完成 15~52 个长时间运行的 SQL 查询(所有这些查询都相似,每个都需要 5 秒以上才能完成)。
经过大量研究,似乎在不阻塞 ASP.Net 线程的情况下最好的方法是使用带有 SQL 查询的 async/await 任务方法:
[HttpPost]
public async Task<JsonResult> Action() {
// initialization stuff
// create tasks to run async SQL queries
ConcurrentBag<Tuple<DateTime, List<long>>> weeklyObsIdBag =
new ConcurrentBag<Tuple<DateTime, List<long>>>();
Task[] taskList = new Task[reportDates.Count()];
int idx = 0;
foreach (var reportDate in reportDates) { //15 <= reportDates.Count() <= 52
var task = Task.Run(async () => {
using (var sioDbContext = new SioDbContext()) {
var historyEntryQueryable = sioDbContext.HistoryEntries
.AsNoTracking()
.AsQueryable<HistoryEntry>();
var obsIdList = await getObsIdListAsync(
historyEntryQueryable,
reportDate
);
weeklyObsIdBag.Add(new Tuple<DateTime,List<long>>(reportDate, obsIdList));
}
});
taskList[idx++] = task;
}
//await for all the tasks to complete
await Task.WhenAll(taskList);
// consume the results from long running SQL queries,
// which is stored in weeklyObsIdBag
}
private async Task<List<long>> getObsIdListAsync(
IQueryable<HistoryEntry> historyEntryQueryable,
DateTime reportDate
) {
//apply reportDate condition to historyEntryQueryable
//run async query
List<long> obsIdList = await historyEntryQueryable.Select(he => he.ObjectId)
.Distinct()
.ToListAsync()
.ConfigureAwait(false);
return obsIdList;
}
进行此更改后,完成此操作所需的时间大大减少,因为现在我可以同时执行多个 (15~52) 个异步 SQL 查询并等待它们完成,而不是按顺序运行它们。但是,用户开始遇到很多超时问题,例如:
(from Elmah error log)
"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool.
This may have occurred because all pooled connections were in use and max pool size was
reached."
"The wait operation timed out"
是线程饥饿造成的吗?我有一种感觉,我可能使用线程池中的太多线程来实现我想要的,但我认为这应该不是问题,因为我使用 async/await 来防止所有线程被阻塞。
如果事情不能以这种方式进行,那么执行多个长时间运行的 SQL 查询的最佳做法是什么?
【问题讨论】:
-
消费者-生产者模式在这里会有所帮助 + 消息代理,例如 rabbitmq。客户端可以在处理任务时检查任务的 JS 状态。
-
Windows Services 其他长期运行任务的解决方案
标签: asp.net multithreading asynchronous async-await task-parallel-library