Создание зависимостей для выполнения задач, которые зависят от других задач

С помощью зависимостей задач Batch можно создавать задачи, выполнение которых планируется на вычислительных узлах после завершения одной или нескольких родительских задач. Например, можно создать задание, которое отрисовывает каждый кадр трехмерного фильма с помощью отдельных параллельных задач. Конечная задача объединяет отрисованные кадры в готовый фильм только после успешной отрисовки всех кадров. Иными словами, последняя задача зависит от предыдущих родительских задач.

Ниже описано несколько ситуаций, в которых удобно использовать зависимости задач. Их можно использовать для:

  • Рабочие нагрузки типа MapReduce в облаке.
  • заданий, задачи обработки данных которых можно представить в виде направленного ациклического графа (DAG);
  • Процессы предварительной отрисовки и после отрисовки, в которых каждая задача должна завершиться до начала следующей задачи.
  • любых других заданий, в которых подчиненные задачи зависят от выходных данных вышестоящих задач.

По умолчанию выполнение зависимых задач планируется только после успешного завершения родительской задачи. При необходимости можно указать действие зависимости, чтобы переопределить поведение по умолчанию и выполнить зависимые задачи даже при сбое родительской задачи.

В этой статье рассматривается, как настроить зависимости между задачами с помощью библиотеки Batch .NET. Сначала в этой статье описывается, как включить зависимость задачи в заданиях, а потом рассматривается, как настроить задачу с зависимостями. Мы также опишем, как указать действие зависимости, запускающее зависимые задачи при сбое родительской задачи. Наконец, мы обсудим поддерживаемые Batch сценарии зависимостей.

Включить зависимости задач

Чтобы использовать зависимости задач в приложении Batch, сначала необходимо настроить задание на использование зависимостей задач. В .NET API Batch включите это в объекте BatchJobCreateOptions, задав свойству UsesTaskDependencies значение true:

BatchJobCreateOptions unboundJob = new BatchJobCreateOptions("job001", new BatchPoolInfo() { PoolId = "pool001" })
{
    // IMPORTANT: This is REQUIRED for using task dependencies.
    UsesTaskDependencies = true
};
await batchClient.CreateJobAsync(unboundJob);

Приведенный выше фрагмент кода batchClient представляет собой экземпляр класса BatchClient.

Создать зависимые задачи

Чтобы создать задачу, зависящую от выполнения одной или нескольких родительских задач, можно указать, что эта задача "зависит" от других задач. В Batch .NET настройте свойство BatchTaskCreateOptions.DependsOn, указав экземпляр класса BatchTaskDependencies:

// Task 'Flowers' depends on completion of both 'Rain' and 'Sun'
// before it is run.
BatchTaskCreateOptions flowers = new BatchTaskCreateOptions("Flowers", "cmd.exe /c echo Flowers")
{
    DependsOn = new BatchTaskDependencies()
    {
        TaskIds = { "Rain", "Sun" }
    }
};

Этот фрагмент кода создает зависимую задачу с идентификатором задачи Flowers. Задача Flowers зависит от задач Rain и Sun. Задача "Цветы" будет запланирована на выполнение на вычислительном узле только после успешного выполнения задач "Дождь" и "Солнце".

Примечание.

По умолчанию задача считается успешно завершенной, если она находится в состоянии завершения, а код выхода — 0. В Batch .NET это означает, что значение свойства BatchTask.State равно Completed, а значение свойства BatchTask BatchTaskExecutionInfo.ExitCode равно 0. Чтобы узнать, как изменить эти значения, см. раздел Действия зависимостей.

сценарии зависимостей

Есть три основных сценария зависимостей задач, которые можно использовать в пакетной службе Azure: "один к одному", "один ко многим" и зависимость диапазона идентификаторов задач. Эти три сценария можно объединить, чтобы получить четвертый — "многие ко многим".

Сценарий Пример Иллюстрация
Один к одному taskB зависит от taskA

taskB не будет запланировано на выполнение до тех пор, пока задачаA не завершится успешно

Схема, на которой показан сценарий зависимостей задач
Один ко многим taskC зависит от taskA и taskB

taskC не будет запланирован для выполнения до успешного завершения taskA и taskB.

Схема, на которой показан сценарий зависимостей задач
Диапазон идентификаторов задач Задача taskD зависит от выполнения ряда задач. Она не будет запланирована для выполнения до тех пор, пока задачи с идентификаторами

1 – 10 не будут успешно завершены.

Схема, на которой показан сценарий зависимостей диапазона идентификаторов задач.

Совет

Вы можете создавать отношения многие ко многим, например, задачи C, D, E и F зависят от задач A и B. Это полезно в схематах параллелизованной предварительной обработки, где последующие задачи зависят от выхода нескольких предыдущих задач.

В примерах в этом разделе зависимая задача выполняется только после успешного завершения родительской задачи. Это поведение по умолчанию для зависимой задачи. После сбоя родительской задачи можно запустить зависимую задачу, указав действие зависимости, чтобы переопределить поведение по умолчанию.

Один к одному

При взаимосвязи "один к одному" задача зависит от успешного выполнения одной родительской задачи. Чтобы создать зависимость, задайте batchTaskDependencies.TaskIds одним идентификатором задачи при заполнении свойства BatchTaskCreateOptions.DependOn .

IList<BatchTaskCreateOptions> tasks = new List<BatchTaskCreateOptions>
{
    // Task 'taskA' doesn't depend on any other tasks
    new BatchTaskCreateOptions("taskA", "cmd.exe /c echo taskA"),

    // Task 'taskB' depends on completion of task 'taskA'
    new BatchTaskCreateOptions("taskB", "cmd.exe /c echo taskB")
    {
        DependsOn = new BatchTaskDependencies() { TaskIds = { "taskA" } }
    },
};

Один-ко-многим

При взаимосвязи "один ко многим" задача зависит от успешного выполнения нескольких родительских задач. Чтобы создать зависимость, заполните batchTaskDependencies.TaskIds идентификаторами родительских задач при заполнении свойства BatchTaskCreateOptions.DependOn .

IList<BatchTaskCreateOptions> tasks = new List<BatchTaskCreateOptions>
{
    // 'Rain' and 'Sun' don't depend on any other tasks
    new BatchTaskCreateOptions("Rain", "cmd.exe /c echo Rain"),
    new BatchTaskCreateOptions("Sun", "cmd.exe /c echo Sun"),

    // Task 'Flowers' depends on completion of both 'Rain' and 'Sun'
    // before it is run.
    new BatchTaskCreateOptions("Flowers", "cmd.exe /c echo Flowers")
    {
        DependsOn = new BatchTaskDependencies() { TaskIds = { "Rain", "Sun" } }
    },
};

Внимание

Создание связанной задачи не удаётся, если общая длина идентификаторов родительских задач превышает 64 000 символов. Чтобы указать большое количество родительских задач, рассмотрите возможность использования диапазона идентификаторов задач.

Диапазон идентификаторов задач

В зависимости от диапазона родительских задач задача зависит от завершения задач, чьи идентификаторы входят в указанный вами диапазон.

Чтобы создать зависимость, заполните BatchTaskDependencies.TaskIdRanges значением BatchTaskIdRange при заполнении свойства BatchTaskCreateOptions.DependsOn.

Внимание

При использовании диапазонов идентификаторов задач для зависимостей выбираются только задачи с идентификаторами, представленными целыми числами. Например, диапазон 1..10 выбирает задачи 3 и 7, но не 5flamingoes.

Начальные нули не являются значительными при оценке зависимостей диапазона, поэтому задачи с идентификаторами строк 4, 04 и 004 находятся в пределах диапазона. Поскольку все они обрабатываются как задача , первая выполненная задача удовлетворяет зависимости.

Чтобы зависимая задача запустилась, каждая задача в указанном диапазоне должна удовлетворять зависимости — либо успешно завершиться, либо завершиться с ошибкой, для которой у действия зависимости установлено значение Satisfy.

IList<BatchTaskCreateOptions> tasks = new List<BatchTaskCreateOptions>
{
    // Tasks 1, 2, and 3 don't depend on any other tasks. Because
    // we will be using them for a task range dependency, we must
    // specify string representations of integers as their ids.
    new BatchTaskCreateOptions("1", "cmd.exe /c echo 1"),
    new BatchTaskCreateOptions("2", "cmd.exe /c echo 2"),
    new BatchTaskCreateOptions("3", "cmd.exe /c echo 3"),

    // Task 4 depends on a range of tasks, 1 through 3
    new BatchTaskCreateOptions("4", "cmd.exe /c echo 4")
    {
        // To use a range of tasks, their ids must be integer values.
        // Note that we pass integers as parameters to BatchTaskIdRange,
        // but their ids (above) are string representations of the ids.
        DependsOn = new BatchTaskDependencies()
        {
            TaskIdRanges = { new BatchTaskIdRange(1, 3) }
        }
    },
};

Действия зависимостей

По умолчанию зависимые задачи или набор задач выполняются только после успешного завершения родительской задачи. В некоторых случаях может потребоваться запустить зависимые задачи даже в случае сбоя родительской задачи. Поведение по умолчанию можно переопределить, указав действие зависимости, которое показывает, можно ли выполнить зависимую задачу.

Например, предположим, что зависимая задача ожидает данных, которые поступят после завершения предшествующей задачи. При сбое восходящей задачи зависимая задача все равно может быть выполнена с использованием старых данных. В этом случае с помощью действия зависимости можно указать, что зависимая задача может быть выполнена в случае сбоя родительской задачи.

Действие зависимости основано на условии выхода родительской задачи. Вы можете указать действие зависимости для любого из приведенных ниже условий выхода.

  • При возникновении ошибки предварительной обработки.
  • При возникновении ошибки отправки файла. Если задача завершается с кодом выхода, который был задан с помощью свойства exitCodes или exitCodeRanges, а затем возникает ошибка отправки файла, действие, указанное в коде выхода, имеет более высокий приоритет.
  • Задача завершается всякий раз, когда её код выхода соответствует значению, заданному свойством ExitCodes.
  • Когда задача завершает работу с кодом выхода, который попадает в диапазон, указанный свойством ExitCodeRanges .
  • По умолчанию, если задача завершается с кодом выхода, не определенным exitCodes или ExitCodeRanges, или если задача завершается с ошибкой предварительной обработки и свойством PreProcessingError не задано, или если задача завершается ошибкой отправки файлов, а свойство FileUploadError не задано.

Для .NET эти условия определяются как свойства класса ExitConditions.

Чтобы указать действие зависимости, задайте для условия выхода свойство ExitOptions.DependencyAction одним из следующих параметров:

  • Satisfy: зависимые задачи можно будет выполнить, если родительская задача завершится с указанной ошибкой.
  • Блок. Указывает, что зависимые задачи не могут выполняться.

По умолчанию для свойства DependencyAction заданы значение Satisfy для кода выхода 0 и значение Block для всех остальных условий выхода.

В следующем фрагменте кода настраивается свойство DependencyAction родительской задачи. Если родительская задача завершает работу с ошибкой предварительной обработки или с указанными кодами ошибок, зависимые задачи блокируются. Если родительская задача завершает работу с любой другой ошибкой, отличной от нуля, зависимые задачи могут выполняться.

IList<BatchTaskCreateOptions> tasks = new List<BatchTaskCreateOptions>
{
    // Task A is the parent task.
    new BatchTaskCreateOptions("A", "cmd.exe /c echo A")
    {
        // Specify exit conditions for task A and their dependency actions.
        ExitConditions = new ExitConditions()
        {
            // If task A exits with a pre-processing error, block any downstream tasks (in this example, task B).
            PreProcessingError = new ExitOptions()
            {
                DependencyAction = DependencyAction.Block
            },
            // If task A exits with the specified error codes, block any downstream tasks (in this example, task B).
            ExitCodes =
            {
                new ExitCodeMapping(10, new ExitOptions() { DependencyAction = DependencyAction.Block }),
                new ExitCodeMapping(20, new ExitOptions() { DependencyAction = DependencyAction.Block })
            },
            // If task A succeeds or fails with any other error, any downstream tasks become eligible to run
            // (in this example, task B).
            DefaultExitOptions = new ExitOptions()
            {
                DependencyAction = DependencyAction.Satisfy
            }
        }
    },
    // Task B depends on task A. Whether it becomes eligible to run depends on how task A exits.
    new BatchTaskCreateOptions("B", "cmd.exe /c echo B")
    {
        DependsOn = new BatchTaskDependencies() { TaskIds = { "A" } }
    },
};

Пример кода

В примере проекта TaskDependencies на сайте GitHub показано:

  • Как включить зависимость задачи от задания.
  • как создать задачи, которые зависят от других задач;
  • как выполнить эти задачи в пуле вычислительных узлов.

Следующие шаги