Pergunta

O que é a melhor maneira de executar um par de tarefas juntos e se uma tarefa falhar, então os próximos tarefas não deve ser concluída? Eu sei que se fosse as operações de banco de dados, então eu deveria ter usado Transações mas eu estou falando sobre diferentes tipos de operações como o seguinte:

Todas as tarefas devem passar:

SendEmail ArchiveReportsInDatabase CreateAFile

No cenário acima de tudo as tarefas deve passar ou então toda a operação de lote deve ser reversão.

Foi útil?

Solução

As exceções são geralmente bom para este tipo de coisa. código de pseudo-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);
    ...
}

Melhor ainda se os seus métodos lançam exceções-se:

try {
    SendEmail();
    ArchiveReportsInDatabase();
    CreateAFile();
    ...

} catch (Exception) {
    LogError(Exception);
    ...
}

A very nice resultado deste estilo é que seu código não fica cada vez mais recuado como você se move para baixo da cadeia de tarefas; todas as suas chamadas de método permanecer no mesmo nível de recuo. Demasiada recuo torna o código mais difícil de ler.

Além disso, você tem um único ponto no código de tratamento de erros, registro reversão etc.

Outras dicas

Rollbacks estão difíceis - AFAIK, não há realmente apenas 2 maneiras de ir sobre ele. De qualquer uma duas fases protocolo, ou transações de compensação . Você realmente tem que encontrar uma maneira de estruturar suas tarefas em uma dessas modas.

Normalmente, a melhor idéia é aproveitar rígidos tecnologias trabalho e uso de outras pessoas que já têm 2PC ou compensação construído dentro. Essa é uma razão que RDBMS são tão populares.

Assim, os detalhes são dependentes tarefa ... mas o padrão é bastante fácil:

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

Claro que, para as suas tarefas específicas - você pode estar com sorte.

  • Obviamente, o trabalho RDBMS já pode ser envolvido em uma transação.
  • Se você estiver em Vista ou Server 2008, então você obtém Transactional NTFS para cobertura seu cenário CreateFile.
  • E-mail é um pouco mais complicado - Eu não sei de qualquer 2PC ou compensadores em torno dele (eu só estaria um pouco surpreso se alguém apontou que Exchange tem um, embora), então eu provavelmente usar MSMQ para escrever uma notificação e deixar um assinante buscá-lo e, eventualmente, enviar e-mail. Nesse ponto, a transação realmente cobre apenas enviar a mensagem para a fila, mas isso é provavelmente bom o suficiente.

Todos estes podem participar de um System.Transactions Transação , então você deve estar em forma muito boa.

em C #

retorno SendEmail () && ArchiveResportsInDatabase () && CreateAFile ();

Outra idéia:

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

Um par de sugestões:

Em um cenário distribuído, algum tipo de confirmação de duas fases protocolo pode ser necessária. Essencialmente, você envia todos os participantes uma mensagem dizendo "Prepare-se para fazer X". Cada participante deve, então, enviar uma resposta dizendo "OK, eu garanto que posso fazer X" ou "Não, não posso fazer isso." Se todos os participantes garantem que possam completar, em seguida, enviar a mensagem dizendo-lhes para fazê-lo. As "garantias" podem ser tão rigorosas como necessário.

Outra abordagem é fornecer algum tipo de desfazer mecanismo para cada operação, então tem lógica como este:

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

(Você não gostaria que seu código para olhar assim;. Esta é apenas uma ilustração de como a lógica deve fluir)

Se o seu idioma permite que ele, isso é muito arrumado:

  1. Coloque suas tarefas em uma variedade de blocos de código ou ponteiros de função.
  2. Iterate sobre a matriz.
  3. Break se qualquer falha bloco retorna.

Você não mencionou qual linguagem de programação / ambiente que você está usando. Se ele é o .NET Framework, você pode querer dar uma olhada este artigo . Ele descreve a Concorrência e Controle Runtime do Robotics Studio da Microsoft, que permite que você aplique todos os tipos de regras em um conjunto de eventos (assíncrono): por exemplo, você pode esperar por qualquer número deles para completar, cancelar se um evento de falha, etc. pode executar as coisas em vários segmentos, bem como, de modo a obter um método muito poderoso de fazer coisas.

Você não especificar o seu ambiente. Em Unix shell script, o operador && faz exatamente isso.

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

SendEmail && ArchiveReportsInDatabase && CreateAFile

Se você estiver usando uma linguagem que usa tipo de circuito de avaliação (Java e C # não), você pode simplesmente fazer:

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

Isso irá retornar verdadeiro se todas as funções retornam verdadeiro, e parar logo que a primeira falsa retorno um.

Para realmente fazer isso direito, você deve usar um padrão de mensagens asyncronous. Acabei de terminar um projeto onde eu fiz isso usando NServiceBus e MSMQ.

Basicamente, cada etapa acontece através do envio de uma mensagem para uma fila. Quando NServiceBus encontra mensagens em espera na fila que chama seu método Handle correspondente a esse tipo de mensagem. Desta maneira, cada passo individual é independentemente failable e repetível. Se um passo falhar as extremidades de mensagens em uma fila de erro que você possa facilmente voltar a tentar mais tarde.

Estas soluções de código pura sendo sugeridas não são tão robustos, pois se um passo falhar, você não teria nenhuma maneira para repetir apenas a um passo no futuro e você teria que implementar o código de reversão que não é ainda possível em alguns casos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top