質問
いくつかのタスクを一緒に実行する最良の方法は何ですか。1つのタスクが失敗した場合、次のタスクを完了しないでください。データベース操作であるかどうかはわかりますが、トランザクションを使用する必要がありますが、次のようなさまざまなタイプの操作について説明しています:
すべてのタスクがパスする必要があります:
SendEmail ArchiveReportsInDatabase CreateAFile
上記のシナリオでは、すべてのタスクが成功するか、バッチ操作全体がロールバックされる必要があります。
解決
例外は、一般的にこの種のものには適しています。擬似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つの方法しかありません。 2フェーズコミットプロトコル、または補償取引。これらの方法のいずれかでタスクを構造化する方法を見つける必要があります。
通常、より良いアイデアは、他の人々のハードワークを活用し、すでに2PCまたは補償が組み込まれているテクノロジーを使用することです。それがRDBMSが非常に人気がある理由の1つです。
したがって、詳細はタスクに依存します...しかし、パターンはかなり簡単です:
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またはServer 2008を使用している場合は、トランザクションNTFS を取得できますCreateFileシナリオ。
- 電子メールは少し複雑です-2PCやその周辺の補償器は知りません(ただし、Exchangeに電子メールがあると指摘された場合は少し驚いただけです)ので、おそらく MSMQ で通知を作成し、サブスクライバーがそれを選択して最終的にメールで送信できるようにします。その時点で、トランザクションは本当にメッセージをキューに送信するだけで十分ですが、それで十分でしょう。
これらはすべて、 System.Transactions トランザクションに参加できます。 、それであなたはかなり良い状態になっているはずです。
C#で
SendEmail()を返す&amp;&amp; ArchiveResportsInDatabase()&amp;&amp; CreateAFile();
別のアイデア:
try {
task1();
task2();
task3();
...
taskN();
}
catch (TaskFailureException e) {
dealWith(e);
}
いくつかの提案:
分散シナリオでは、ある種の2フェーズコミットプロトコルが必要になる場合があります。基本的に、すべての参加者に「Xを実行する準備をしてください」というメッセージを送信します。各参加者は、「OK、私はXを実行できることを保証します」という応答を送信する必要があります。または「いいえ、できません。」すべての参加者が完了できることを保証したら、それを行うように伝えるメッセージを送信します。 「保証」必要に応じて厳密にすることができます。
別のアプローチは、各操作に何らかの種類の取り消しメカニズムを提供し、次のようなロジックを使用することです。
try:
SendEmail()
try:
ArchiveReportsInDatabase()
try:
CreateAFile()
except:
UndoArchiveReportsInDatabase()
raise
except:
UndoSendEmail()
raise
except:
// handle failure
(コードをそのように見せたくないでしょう。これは、ロジックがどのように流れるべきかの単なる例示です。)
言語で許可されている場合、これは非常に整頓されています:
- タスクをコードブロックまたは関数ポインターの配列に入れます。
- 配列を反復処理します。
- いずれかのブロックが失敗を返した場合は中断します。
使用しているプログラミング言語/環境については言及しませんでした。 .NET Frameworkの場合は、この記事。 MicrosoftのRobotics Studioの同時実行および制御ランタイムについて説明します。これにより、一連の(非同期)イベントにあらゆる種類のルールを適用できます。など。複数のスレッドで実行することもできるため、非常に強力な方法を使用できます。
環境を指定しません。 Unixシェルスクリプトでは、&amp;&amp;オペレーターはこれを行います。
SendEmail () {
# ...
}
ArchiveReportsInDatabase () {
# ...
}
CreateAFile () {
# ...
}
SendEmail && ArchiveReportsInDatabase && CreateAFile
sort-circuit評価(Javaを使用する言語を使用している場合およびC#を実行します)、単に実行できます:
return SendEmail() && ArchiveResportsInDatabase() && CreateAFile();
これは、すべての関数がtrueを返す場合にtrueを返し、最初の関数がfalseを返すとすぐに停止します。
実際に正しく実行するには、非同期メッセージングパターンを使用する必要があります。 nServiceBus とMSMQを使用してこれを行ったプロジェクトを終了しました。
基本的に、各ステップはメッセージをキューに送信することで発生します。 nServiceBusは、キューで待機しているメッセージを見つけると、そのメッセージタイプに対応するHandleメソッドを呼び出します。このようにして、各ステップは個別に失敗および再試行可能です。 1つのステップが失敗すると、メッセージはエラーキューに格納されるため、後で簡単に再試行できます。
提案されているこれらの純粋なコードのソリューションは堅牢ではありません。ステップが失敗した場合、将来その1つのステップのみを再試行する方法がなく、ロールバックコードを実装する必要があります。いくつかのケース。