代码之家  ›  专栏  ›  技术社区  ›  azamsharp

执行顺序操作的最佳实践

  •  3
  • azamsharp  · 技术社区  · 16 年前

    一起执行两个任务的最佳方法是什么?如果一个任务失败,那么下一个任务就不应该完成?我知道如果它是数据库操作,那么我应该使用事务,但我正在讨论不同类型的操作,如以下所示:

    所有任务必须通过:

    发送电子邮件 数据库中的存档报告 创造创造

    在上述场景中,所有任务都必须通过,否则整个批处理操作必须回滚。

    10 回复  |  直到 12 年前
        1
  •  0
  •   Ates Goral    16 年前

    例外对这类事情通常是有利的。伪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);
        ...
    }
    

    这种风格的一个非常好的结果是,当沿着任务链向下移动时,代码不会越来越缩进;所有方法调用都保持在相同的缩进级别。太多的缩进会使代码更难读取。

    此外,代码中还有一个单点用于错误处理、日志记录、回滚等。

        2
  •  4
  •   Mark Brackett Achilles Ram Nakirekanti    16 年前

    后仰是很难的-阿法克,只有两种方法可以做到。要么是 2 phase commit protocol compensating transactions . 你真的必须找到一种方法,以这种方式来安排你的任务。

    通常,更好的办法是利用他人的努力工作,使用已经内置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;
       }
    }
    

    当然,对于你的特定任务,你可能会很幸运。

    • 显然,RDBMS工作已经可以包装在事务中了。
    • 如果您在Vista或Server2008上,那么 Transactional NTFS 覆盖您的创建文件场景。
    • 电子邮件有点棘手-我不知道它周围有任何2PC或补偿(不过,如果有人指出交换有一个,我会有点惊讶),所以我可能会使用 MSMQ 写一个通知,让订户接收它,并最终通过电子邮件发送。在这一点上,您的事务实际上只涉及将消息发送到队列,但这可能已经足够好了。

    所有这些都可以参与 System.Transactions 事务处理,所以您应该状态良好。

        3
  •  2
  •   Echostorm    16 年前

    在C中

    返回sendemail()&archiveresportsindatabase()&createafile();

        4
  •  1
  •   easeout    16 年前

    另一个想法:

    try {
        task1();
        task2();
        task3();
        ...
        taskN();
    }
    catch (TaskFailureException e) {
        dealWith(e);
    }
    
        5
  •  1
  •   Kristopher Johnson    16 年前

    一些建议:

    在分布式场景中,可能需要某种两阶段提交协议。本质上,您向所有参与者发送一条消息,说“准备做X”。然后,每个参与者都必须发送一条回复:“好的,我保证我可以做X”或“不,不能做”。如果所有参与者都保证他们可以完成,那么就发送一条消息告诉他们要做。“保证”可以根据需要严格执行。

    另一种方法是为每个操作提供某种撤消机制,然后具有如下逻辑:

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

    (您不希望代码看起来像那样;这只是逻辑应该如何流动的一个说明。)

        6
  •  0
  •   easeout    16 年前

    如果你的语言允许,这是非常整洁的:

    1. 将任务放入代码块或函数指针数组中。
    2. 迭代数组。
    3. 如果任何块返回失败,则中断。
        7
  •  0
  •   Yuval    16 年前

    您没有提到正在使用的编程语言/环境。如果是.NET框架,您可能想看看 this article .它描述了来自Microsoft Robotics Studio的并发和控制运行时,它允许您对一组(异步)事件应用各种规则:例如,您可以等待任意数量的事件完成,如果一个事件失败则取消,等等。它也可以在多个线程中运行,因此您可以获得一种非常强大的执行方法东西。

        8
  •  0
  •   Andru Luvisi    16 年前

    您没有指定环境。在Unix Shell脚本中,运算符仅执行此操作。

    SendEmail () {
      # ...
    }
    ArchiveReportsInDatabase () {
      # ...
    }
    CreateAFile () {
      # ...
    }
    
    SendEmail && ArchiveReportsInDatabase && CreateAFile
    
        9
  •  0
  •   Eugene Katz    16 年前

    如果你使用的语言 sort-circuit evaluation (Java和C语言),你可以简单地做:

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

    如果所有函数都返回true,则返回true,第一个函数返回false时立即停止。

        10
  •  0
  •   JC.    16 年前

    要真正做到正确,您应该使用异步消息模式。我刚完成一个项目,我用 nServiceBus 和MSMQ。

    基本上,每一步都是通过向队列发送消息来完成的。当nservicebus发现队列中等待的消息时,它调用与该消息类型对应的handle方法。这样,每个单独的步骤都是独立的故障和可重试的。如果一个步骤失败,则消息将结束在错误队列中,以便您稍后可以轻松地重试。

    这些被建议的纯代码解决方案并没有那么健壮,因为如果一个步骤失败了,将来您将无法只重试这一步,并且您将不得不实现回滚代码,在某些情况下这甚至是不可能的。