Подтвердить что ты не робот

Как создать планировщик, который никогда не выполняет несколько задач одновременно, используя async-wait?

Я хочу реализовать класс или шаблон, который гарантирует, что я никогда не буду выполнять более одной задачи одновременно для определенного набора операций (HTTP-вызовы). Вызовы Заданий могут возникать из разных потоков в произвольные моменты времени. Я хочу использовать шаблон async-await, чтобы вызывающий мог обрабатывать исключения, завершая вызов в try-catch.

Вот иллюстрация предполагаемого потока выполнения:

enter image description here

Псевдокод от вызывающего:

try {
    Task someTask = GetTask();
    await SomeScheduler.ThrottledRun(someTask);
} 
catch(Exception ex) { 
    // Handle exception
}

Класс Task здесь может вместо этого быть классом Action в зависимости от решения.

Обратите внимание, что когда я использую слово "Расписание" в этом вопросе, я не обязательно его использую в отношении .NET Task Scheduler. Я не очень хорошо знаю библиотеку асинхронного ожидания, чтобы знать, с какого угла и с какими инструментами подходит эта проблема. TaskScheduler может иметь значение здесь, и это может быть не так. Я прочитал документ TAP pattern и нашел шаблоны, которые почти решают эту проблему, но не совсем (глава о чередовании).

4b9b3361

Ответ 1

В .NET 4.5 существует новый тип ConcurrentExclusiveSchedulerPair (я не помню, включен ли он в Async CTP) и вы можете использовать его ExclusiveScheduler, чтобы ограничить выполнение одним Task за раз.

Рассмотрим структурирование вашей проблемы как Dataflow. Легко просто передать TaskScheduler в опции блока для частей потока данных, которые вы хотите ограничить.

Если вы не хотите (или не можете) использовать Dataflow, вы можете сделать что-то подобное себе. Помните, что в TAP вы всегда возвращаете запущенные задачи, поэтому у вас нет "создания", отделенного от "планирования", как в TPL.

Вы можете использовать ConcurrentExclusiveSchedulerPair для планирования Action (или async lambdas без возвращаемых значений) следующим образом:

public static ConcurrentExclusiveSchedulerPair schedulerPair =
    new ConcurrentExclusiveSchedulerPair();
public static TaskFactory exclusiveTaskFactory =
    new TaskFactory(schedulerPair.ExclusiveScheduler);
...
public static Task RunExclusively(Action action)
{
  return exclusiveTaskFactory.StartNew(action);
}
public static Task RunExclusively(Func<Task> action)
{
  return exclusiveTaskFactory.StartNew(action).Unwrap();
}

Есть несколько вещей, которые следует отметить об этом:

  • Один экземпляр ConcurrentExclusiveSchedulerPair содержит только координаты Task, которые помещаются в очередь для своих планировщиков. Второй экземпляр ConcurrentExclusiveSchedulerPair будет независим от первого, поэтому вы должны убедиться, что один и тот же экземпляр используется во всех частях вашей системы, которые вы хотите скоординировать.
  • Метод async будет - по умолчанию - возобновить на том же TaskScheduler, который запустил его. Таким образом, это означает, что если один метод async вызывает другой метод async, метод "child" будет "наследовать" родительский TaskScheduler. Любой метод async может отказаться от продолжения на TaskScheduler с помощью ConfigureAwait(false) (в этом случае он продолжается непосредственно в пуле потоков).