一起执行几项任务的最佳方法是什么?如果一项任务失败,那么下一项任务不应该完成?我知道如果是数据库操作,那么我应该使用事务,但我在谈论不同类型的操作,如下所示:
所有任务必须通过:
SendEmail ArchiveReportsInDatabase CreateAFile
在上面的场景中,所有任务都必须通过,否则整个批处理操作必须回滚。
答案 0 :(得分:4)
回滚很难--AFAIK,实际上只有两种方法可以实现。可以是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;
}
}
当然,对于您的具体任务 - 您可能会很幸运。
所有这些都可以参与MSMQ交易,所以你应该处于良好的状态。
答案 1 :(得分:2)
在C#中
返回SendEmail()&amp;&amp; ArchiveResportsInDatabase()&amp;&amp; CreateAFile();
答案 2 :(得分:1)
另一个想法:
try {
task1();
task2();
task3();
...
taskN();
}
catch (TaskFailureException e) {
dealWith(e);
}
答案 3 :(得分:1)
一些建议:
在分布式场景中,可能需要某种两阶段提交协议。基本上,您向所有参与者发送一条消息“准备做X”。然后,每个参与者必须发送回复,说“好的,我保证我可以做X”或“不,不能做”。如果所有参与者都保证他们可以完成,那么发送消息告诉他们这样做。 “保证”可以根据需要严格。
另一种方法是为每个操作提供某种撤销机制,然后有这样的逻辑:
try:
SendEmail()
try:
ArchiveReportsInDatabase()
try:
CreateAFile()
except:
UndoArchiveReportsInDatabase()
raise
except:
UndoSendEmail()
raise
except:
// handle failure
(你不希望你的代码看起来像那样;这只是逻辑应该如何流动的例证。)
答案 4 :(得分:0)
如果您的语言允许,这非常整洁:
答案 5 :(得分:0)
您没有提到您正在使用的编程语言/环境。如果是.NET Framework,您可能需要查看this article。它描述了Microsoft的Robotics Studio中的并发和控制运行时,它允许您对一组(异步)事件应用各种规则:例如,您可以等待任意数量的事件完成,如果一个事件失败则取消,它也可以在多个线程中运行,因此您可以获得一种非常强大的方法。
答案 6 :(得分:0)
您没有指定您的环境。在Unix shell脚本中,&amp;&amp;运营商就是这样做的。
SendEmail () {
# ...
}
ArchiveReportsInDatabase () {
# ...
}
CreateAFile () {
# ...
}
SendEmail && ArchiveReportsInDatabase && CreateAFile
答案 7 :(得分:0)
如果您使用的语言使用sort-circuit evaluation(Java和C#),您可以这样做:
return SendEmail() && ArchiveResportsInDatabase() && CreateAFile();
如果所有函数都返回true,则返回true,并在第一个函数返回false时立即停止。
答案 8 :(得分:0)
例外情况通常适用于此类事情。伪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);
...
}
这种风格的一个非常好的结果是,当你向下移动任务链时,你的代码不会越来越缩进;您的所有方法调用都保持在相同的缩进级别。过多的缩进会使代码难以阅读。
此外,您在代码中只有一个点用于错误处理,记录,回滚等。
答案 9 :(得分:0)
要真正做到正确,您应该使用异步消息传递模式。我刚刚完成了一个项目,我使用nServiceBus和MSMQ完成了这项工作。
基本上,每个步骤都是通过向队列发送消息来实现的。当nServiceBus发现在队列中等待的消息时,它会调用与该消息类型对应的Handle方法。这样,每个单独的步骤都是独立可用和可重试的。如果一步失败,则消息最终会出现在错误队列中,以便您以后轻松重试。
这些建议的纯代码解决方案并不那么健壮,因为如果一个步骤失败,你将无法再尝试将来的那一步,而你必须实现甚至不可能的回滚代码有些情况。