Question

Quel est le meilleur moyen d'exécuter plusieurs tâches ensemble et si une tâche échouait, les tâches suivantes ne devraient pas être terminées? Je sais que s’il s’agissait des opérations de base de données, j’aurais alors dû utiliser Transactions, mais je parle de différents types d’opérations, comme suit:

Toutes les tâches doivent réussir:

SendEmail ArchiveReportsInDatabase CreateAFile

Dans le scénario ci-dessus, toutes les tâches doivent réussir, sinon l'opération de traitement par lots doit être annulée.

Était-ce utile?

La solution

Les exceptions sont généralement bonnes pour ce genre de chose. Code 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);
    ...
}

Encore mieux si vos méthodes jettent des exceptions elles-mêmes:

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

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

Un résultat très intéressant de ce style est que votre code ne devient pas de plus en plus indenté au fur et à mesure que vous avancez dans la chaîne de tâches; tous vos appels de méthode restent au même niveau d'indentation. Trop d'indentation rend le code plus difficile à lire.

De plus, vous avez un seul point dans le code pour la gestion des erreurs, la journalisation, la restauration, etc.

Autres conseils

Les retours en arrière sont difficiles - autant que je sache, il n'y a vraiment que 2 façons de s'y prendre. Soit un protocole de validation en deux phases , ou transactions compensatrices . Vous devez vraiment trouver un moyen de structurer vos tâches de cette manière.

Généralement, la meilleure idée est de tirer parti du travail acharné d’autres personnes et d’utiliser les technologies qui intègrent déjà 2 PC ou une compensation. C’est une des raisons pour laquelle les SGBDR sont si populaires.

Donc, les spécificités dépendent des tâches ... mais le schéma est assez simple:

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

Bien sûr, pour vos tâches spécifiques - vous avez peut-être de la chance.

  • Évidemment, le travail sur le SGBDR peut déjà être encapsulé dans une transaction.
  • Si vous utilisez Vista ou Server 2008, vous devez NTFS transactionnel couvrir votre scénario CreateFile.
  • L'email est un peu plus compliqué - je ne connais aucun 2PC ni aucun Compensateur autour (je serais un peu surpris si quelqu'un indiquait qu'Exchange en a un), alors j'utiliserais probablement MSMQ pour écrire une notification et permettre à un abonné de la récupérer et de l'envoyer par courrier électronique. À ce stade, votre transaction ne couvre que l'envoi du message à la file d'attente, mais c'est probablement suffisant.

Tous ces éléments peuvent participer à une System.Transactions . , vous devriez donc être en assez bonne forme.

en C #

renvoyer SendEmail () & amp; & amp; ArchiveResportsInDatabase () & amp; & amp; CreateAFile ();

Une autre idée:

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

Quelques suggestions:

Dans un scénario distribué, une sorte de protocole de validation en deux phases peut être nécessaire. Pour l’essentiel, vous envoyez à tous les participants un message disant «Préparez-vous à faire X». Chaque participant doit ensuite envoyer une réponse disant "OK, je garantis que je peux faire X". ou "Non, je ne peux pas le faire". Si tous les participants garantissent qu'ils peuvent terminer, envoyez le message en leur disant de le faire. Les "garanties" peut être aussi strict que nécessaire.

Une autre approche consiste à fournir un mécanisme d'annulation pour chaque opération, puis une logique comme celle-ci:

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

(Vous ne voudriez pas que votre code ressemble à ça; ceci est juste une illustration de la façon dont la logique devrait suivre.)

Si votre langue le permet, la tâche est très soignée:

  1. Placez vos tâches dans un tableau de blocs de code ou de pointeurs de fonction.
  2. Itérer sur le tableau.
  3. Interrompez si un bloc renvoie un échec.

Vous n'avez pas mentionné le langage / environnement de programmation que vous utilisez. S'il s'agit du .NET Framework, consultez cet article . Il décrit le régime d’accès simultané et de contrôle de Robotics Studio de Microsoft, qui vous permet d’appliquer toutes sortes de règles à un ensemble d’événements (asynchrones): par exemple, vous pouvez attendre qu’un nombre quelconque d’entre eux se termine, annuler si un événement échoue, etc. Il peut aussi fonctionner dans plusieurs threads, ce qui vous donne une méthode très puissante.

Vous ne spécifiez pas votre environnement. Dans les scripts de shell Unix, la commande & amp; & amp; l'opérateur ne fait que cela.

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

SendEmail && ArchiveReportsInDatabase && CreateAFile

Si vous utilisez une langue qui utilise la l'évaluation du circuit de tri (Java et C # do), vous pouvez simplement faire:

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

Ceci retournera vrai si toutes les fonctions retournent vrai et s'arrêtera dès que la première retournera faux.

Pour bien faire les choses, utilisez un modèle de messagerie asynchrone. Je viens de terminer un projet dans lequel je l'ai fait avec nServiceBus et MSMQ.

En gros, chaque étape se produit en envoyant un message à une file d'attente. Lorsque nServiceBus trouve des messages en attente dans la file d'attente, il appelle votre méthode Handle correspondant à ce type de message. De cette façon, chaque étape individuelle est disponible et réessayable. Si une étape échoue, le message se termine dans une file d’erreurs afin que vous puissiez le réessayer ultérieurement.

Ces solutions de code pur suggérées ne sont pas aussi robustes, car si une étape échouait, vous ne pourriez pas réessayer dans l’avenir et vous auriez à implémenter du code de restauration, ce qui n’est même pas possible. certains cas.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top