Async и Await with For Loop
У меня есть служба Windows, которая запускает различные задания по расписанию. После определения того, какие задания запускаются, список объектов расписания отправляется методу, который выполняет итерацию по списку и запускает каждое задание. Проблема в том, что некоторые задания могут занять до 10 минут из-за внешних вызовов базы данных.
Моя цель состоит в том, чтобы не иметь одного другого блока заданий в очереди, в основном, более одного запуска за раз. Я думал, что использование async и ожидания может состоять в том, чтобы разрешить это, но я никогда не использовал их раньше.
Текущий код:
public static bool Load(List<Schedule> scheduleList)
{
foreach (Schedule schedule in scheduleList)
{
Load(schedule.ScheduleId);
}
return true;
}
public static bool Load(int scheduleId)
{
// make database and other external resource calls
// some jobs run for up to 10 minutes
return true;
}
Я попробовал обновить этот код:
public async static Task<bool> LoadAsync(List<Schedule> scheduleList)
{
foreach (Schedule schedule in scheduleList)
{
bool result = await LoadAsync((int)schedule.JobId, schedule.ScheduleId);
}
return true;
}
public async static Task<bool> LoadAsync(int scheduleId)
{
// make database and other external resource calls
// some jobs run for up to 10 minutes
return true;
}
Проблема заключается в том, что первый LoadAsync ожидает завершения задания, прежде чем возвращать управление в цикл, а не разрешать запуск всех заданий.
У меня есть два вопроса:
- Высокий уровень. Являются ли aysnc/ожидают лучший выбор или я должен использовать другой подход?
- Что нужно обновить, чтобы цикл мог запускать все задания без блокировки, но не позволять функции возвращаться до тех пор, пока все задания не будут завершены?
Ответы
Ответ 1
Высокий уровень - Асинхронный/ожидающий лучший выбор, или я должен использовать другой подход?
async-await
идеально подходит для того, что вы пытаетесь сделать, что одновременно выгружает несколько связанных с IO задач.
Что нужно обновить, чтобы цикл мог запускать все задания без блокировки, но не позволять функции возвращаться до тех пор, пока все задания не будут завершены?
В настоящий момент ваша петля ждет, потому что вы await
каждый вызов LoadAsync
. То, что вы хотите, состоит в том, чтобы выполнять их все одновременно, а не ждать, пока все они закончат использование Task.WhenAll
:
public async static Task<bool> LoadAsync(List<Schedule> scheduleList)
{
var scheduleTaskList = scheduleList.Select(schedule =>
LoadAsync((int)schedule.JobId, schedule.ScheduleId)).ToList();
await Task.WhenAll(scheduleTaskList);
return true;
}
Ответ 2
Для разветвленных параллельных асинхронных вызовов вы хотите запустить Task для запуска их запуска, но затем обрабатывать их как async future или обещать значения. Вы можете просто синхронизировать/ждать их в конце, когда все будет закончено.
Простейший способ сделать это - превратить ваш for-loop в нечто вроде этого:
List<Task<bool>> jobs = new List<Task<bool>>();
foreach (var schedule in scheduleList)
{
Task<bool> job = LoadAsync((int) schedule.JobId, schedule.ScheduleId); // Start each job
jobs.Add(job);
}
bool[] finishedJobStatuses = await Task.WhenAll(jobs); // Wait for all jobs to finish running
bool allOk = Array.TrueForAll(finishedJobStatuses, p => p);
Ответ 3
Я бы рекомендовал использовать Parallel.ForEach. Он не является асинхронным и выполняет каждую итерацию параллельно. Именно то, что вам нужно.
public static bool Load(IEnumerable<Schedule> scheduleList)
{
// set the number of threads you need
// you can also set CancellationToken in the options
var options = new ParallelOptions { MaxDegreeOfParallelism = 5 };
Parallel.ForEach(scheduleList, options, async schedule =>
{
await Load(schedule.ScheduleId);
});
return true;
}