在ASP.Net MVC

时间:2018-02-05 09:53:13

标签: asp.net multithreading asynchronous async-await task-parallel-library

我有一个动作方法,需要根据用户选择的日期完成15~52个长时间运行的SQL查询(所有这些查询都相似,每个查询完成时间超过5秒)。

经过大量研究后,似乎最好的方法是在不阻塞ASP.Net线程的情况下使用async / await任务方法使用SQL查询:

[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查询的最佳做法是什么?

1 个答案:

答案 0 :(得分:5)

考虑限制正在执行的并发任务的数量,例如:

int concurrentTasksLimit = 5;
List<Task> taskList = new List<Task>();
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.Add(task);
    if (concurrentTasksLimit == taskList.Count)
    {
        await Task.WhenAll(taskList);
        // before clearing the list, you should get the results and store in memory (e.g another list) for later usage...
        taskList.Clear();
    }
}
//await for all the remaining tasks to complete
if (taskList.Any())
    await Task.WhenAll(taskList);

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

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

相关问题