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

StackOverflow https://stackoverflow.com/questions/168560

  •  03-07-2019
  •  | 
  •  

Вопрос

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

Все задания должны быть выполнены:

Отправить почту Архивная база данных отчетов Создать файл

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

Это было полезно?

Решение

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

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 способа сделать это.Либо a протокол 2-фазной фиксации, или компенсирующие операции.Вам действительно нужно найти способ структурировать свои задачи одним из этих способов.

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

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

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

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

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

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

в C#

возвращает отправленную почту() && Архивную базу данных portsindatabase() && CreateAFile();

Еще одна идея:

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

Пара предложений:

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

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

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

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

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

  1. Поместите ваши задачи в массив блоков кода или указателей на функции.
  2. Выполните итерацию по массиву.
  3. Прервитесь, если какой-либо блок вернет сбой.

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

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

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

SendEmail && ArchiveReportsInDatabase && CreateAFile

Если вы используете язык, который использует оценка схемы сортировки (Java и C # делают), вы можете просто сделать:

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

Это вернет значение true, если все функции вернут значение true, и остановится, как только первая из них вернет значение false.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top