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

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

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

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

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

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

Включение зависимостей задач

Чтобы использовать зависимости задач в приложении пакетной службы, сначала необходимо настроить задание для использования зависимостей задач. В .NET пакетной службы включите зависимости задач в классе CloudJob, задав для свойства UserTaskDependencies значение true.

CloudJob unboundJob = batchClient.JobOperations.CreateJob( "job001",
    new PoolInformation { PoolId = "pool001" });

// IMPORTANT: This is REQUIRED for using task dependencies.
unboundJob.UsesTaskDependencies = true;

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

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

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

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

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

Примечание

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

сценарии использования зависимостей

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

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

Выполнение задачи taskB не начнется, пока задача taskA не будет успешно выполнена.

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

Выполнение задачи taskC не начнется, пока задачи taskA иtaskB не будут успешно выполнены.

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

Выполнение задачи taskD не начнется, пока не будут успешно выполнены задачи с идентификаторами от 1 до 10.

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

Совет

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

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

Один к одному

При взаимосвязи "один к одному" задача зависит от успешного выполнения одной родительской задачи. Чтобы создать зависимость, при заполнении свойства CloudTask.DependsOn укажите один идентификатор задачи в статическом методе TaskDependencies.OnId.

// Task 'taskA' doesn't depend on any other tasks
new CloudTask("taskA", "cmd.exe /c echo taskA"),

// Task 'taskB' depends on completion of task 'taskA'
new CloudTask("taskB", "cmd.exe /c echo taskB")
{
    DependsOn = TaskDependencies.OnId("taskA")
},

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

При взаимосвязи "один ко многим" задача зависит от успешного выполнения нескольких родительских задач. Чтобы создать зависимость, при заполнении свойства CloudTask.DependsOn укажите набор идентификаторов конкретных задач в статическом методе TaskDependencies.OnIds.

// 'Rain' and 'Sun' don't depend on any other tasks
new CloudTask("Rain", "cmd.exe /c echo Rain"),
new CloudTask("Sun", "cmd.exe /c echo Sun"),

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

Важно!

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

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

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

Чтобы создать зависимость, укажите первый и последний идентификаторы задач в статическом методе TaskDependencies.OnIdRange при заполнении свойства CloudTask.DependsOn.

Важно!

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

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

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

// 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 CloudTask("1", "cmd.exe /c echo 1"),
new CloudTask("2", "cmd.exe /c echo 2"),
new CloudTask("3", "cmd.exe /c echo 3"),

// Task 4 depends on a range of tasks, 1 through 3
new CloudTask("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 TaskIdRange,
    // but their ids (above) are string representations of the ids.
    DependsOn = TaskDependencies.OnIdRange(1, 3)
},

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

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

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

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

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

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

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

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

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

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

// Task A is the parent task.
new CloudTask("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 List<ExitCodeMapping>
        {
            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).
        Default = new ExitOptions
        {
            DependencyAction = DependencyAction.Satisfy
        }
    }
},
// Task B depends on task A. Whether it becomes eligible to run depends on how task A exits.
new CloudTask("B", "cmd.exe /c echo B")
{
    DependsOn = TaskDependencies.OnId("A")
},

Пример кода

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

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

Дальнейшие действия