Рекомендации по проведению последовательных операций

Каков наилучший способ выполнения нескольких задач вместе, и если одна задача не выполняется, следующие задачи не должны выполняться? Я знаю, были ли это операциями базы данных, тогда я должен был использовать транзакции, но я говорю о различных типах операций, таких как:

Все задачи должны пройти:

SendEmail ArchiveReportsInDatabase CreateAFile

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

10 ответов

Исключения вообще хороши для такого рода вещей. Код псевдо-Java/JavaScript/С++:

try {
 if (!SendEmail()) {
 throw "Could not send e-mail";
 }
 if (!ArchiveReportsInDatabase()) {
 throw "Could not archive reports in database";
 }
 if (!CreateAFile()) {
 throw "Could not create file";
 }
 ...
} catch (Exception) {
 LogError(Exception);
 ...
}

Лучше, если ваши методы сами выдают исключения:

try {
 SendEmail();
 ArchiveReportsInDatabase();
 CreateAFile();
 ...
} catch (Exception) {
 LogError(Exception);
 ...
}

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

Кроме того, у вас есть одна точка в коде для обработки ошибок, ведения журнала, отката и т.д.


Отказы жесткие - AFAIK, на самом деле есть только два способа сделать это. Либо 2-фазный протокол фиксации, либо компенсирующие транзакции, Вам действительно нужно найти способ структурировать свои задачи в одной из этих мод.

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

Итак, особенности зависят от задачи... но шаблон довольно прост:

class Compensator {
 Action Action { get; set; }
 Action Compensate { get; set; }
}
Queue<compensator> actions = new Queue<compensator>(new Compensator[] { 
 new Compensator(SendEmail, UndoSendEmail),
 new Compensator(ArchiveReportsInDatabase, UndoArchiveReportsInDatabase),
 new Compensator(CreateAFile, UndoCreateAFile)
});
Queue<compensator> doneActions = new Queue<compensator>();
while (var c = actions.Dequeue() != null) {
 try {
 c.Action();
 doneActions.Add(c);
 } catch {
 try {
 doneActions.Each(d => d.Compensate());
 } catch (EXception ex) {
 throw new OhCrapException("Couldn't rollback", doneActions, ex);
 }
 throw;
 }
}
</compensator></compensator></compensator></compensator>

Конечно, для ваших конкретных задач - вам может быть повезло.

  • Очевидно, что работа СУРБД уже может быть завершена транзакцией.
  • Если вы используете Vista или Server 2008, вы получаете Transactional NTFS для покрытия вашего сценария CreateFile.
  • Электронная почта немного сложнее - я не знаю ни одного 2PC или компенсатора вокруг него (я был бы немного удивлен, если бы кто-то указал, что Exchange имеет один), поэтому я бы, вероятно, использовал MSMQ, чтобы написать уведомление и позволить подписчику поднять его и в конечном итоге отправить по электронной почте. В этот момент ваша транзакция действительно охватывает просто отправку сообщения в очередь, но это, вероятно, достаточно хорошо.

Все они могут участвовать в транзакции System.Transactions, поэтому вы должны быть в довольно хорошей форме.


в С#

return SendEmail() && & ArchiveResportsInDatabase() && & CreateAFile();


Несколько предложений:

В распределенном сценарии может потребоваться какой-то двухфазный протокол фиксации. По сути, вы отправляете всем участникам сообщение "Подготовка к X". Затем каждый участник должен отправить ответ "ОК, я гарантирую, что могу сделать X" или "Нет, не могу этого сделать". Если все участники гарантируют, что они могут заполнить, отправьте сообщение, в котором сообщается, что нужно сделать это. "Гарантии" могут быть настолько строгими, насколько это необходимо.

Другим подходом является предоставление некоторого механизма отмены для каждой операции, а затем логика:

try:
 SendEmail()
 try:
 ArchiveReportsInDatabase()
 try:
 CreateAFile()
 except:
 UndoArchiveReportsInDatabase()
 raise
 except:
 UndoSendEmail()
 raise
except:
 // handle failure

(Вы бы не хотели, чтобы ваш код выглядел так, это просто иллюстрация того, как должна протекать логика.)


Другая идея:

try {
 task1();
 task2();
 task3();
 ...
 taskN();
}
catch (TaskFailureException e) {
 dealWith(e);
}


Если ваш язык позволяет это, это очень аккуратно:

  • Поместите свои задачи в массив блоков кода или указателей функций.
  • Итерации по массиву.
  • Перерыв, если какой-либо блок возвращает сбой.


Вы не указали, какой язык/среду программирования вы используете. Если это .NET Framework, вы можете взглянуть на эту статью. В нем описывается Concurrency и контрольная время выполнения из Microsoft Robotics Studio, что позволяет применять всевозможные правила для множества (асинхронных) событий: например, вы можете дождаться завершения их любого числа, отменить, если одно событие сбой и т.д. Он может работать и в нескольких потоках, поэтому вы получаете очень мощный способ делать вещи.


Чтобы сделать это правильно, вы должны использовать шаблон асинхронного обмена сообщениями. Я только что закончил проект, где я это сделал, используя nServiceBus и MSMQ.

В принципе, каждый шаг происходит, отправляя сообщение в очередь. Когда nServiceBus находит сообщения, ожидающие очереди, он вызывает ваш метод Handle, соответствующий этому типу сообщения. Таким образом, каждый отдельный шаг независим от отказа и может быть повторен. Если один шаг не удается, сообщение заканчивается в очереди ошибок, поэтому вы можете легко повторить его позже.

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


Вы не указываете свою среду. В сценариях оболочки Unix, && оператор делает именно это.

SendEmail () {
 # ...
}
ArchiveReportsInDatabase () {
 # ...
}
CreateAFile () {
 # ...
}
SendEmail && ArchiveReportsInDatabase && CreateAFile


Если вы используете язык, который использует сортировку-сортировку (Java и С# do), вы можете просто сделать:

return SendEmail() && ArchiveResportsInDatabase() && CreateAFile();

Это вернет true, если все функции возвращают true и останавливаются, как только первый возвращает false.

licensed under cc by-sa 3.0 with attribution.