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;
}