.NET 2.0でのトランザクション処理

300 秒でズバリ !! System.Transactions

http://www.microsoft.com/japan/seminar/msdn/300secvs2005/SystemdotTransactions/play.asp

System.Transactions は .NET Framework 2.0 で新たに追加されたトランザクションの統一プログラミング モデルです。このセッションでは、System.Transactions の概要についてご紹介します。

PowerPointと音声で説明してくれます。(IEで見ないとダメ)

10 行でズバリ !! TransactionScope の利用 (C#)

http://www.microsoft.com/japan/msdn/thisweek/300x10/Phase3/Transaction_Scope/cs.aspx


// Button2 押下時の処理
// データセットの内容に基づいてテーブルを更新
private void button2_Click(object sender, EventArgs e)
{
try
{
// TransactionScopeを生成し、トランザクションの範囲を指定
using (TransactionScope tx = new TransactionScope())
{
// 以下はトランザクションの範囲内:
// トランザクショナルなリソースに対する処理は、
// 暗黙的にトランザクションとして処理される
ProductCategoryTableAdapter adapter =
new ProductCategoryTableAdapter();
adapter.Update(adventureWorksDataSet1.ProductCategory);
// Completeメソッドの呼び出しまでがトランザクションの範囲
// トランザクションをコミットする
tx.Complete();
}
// トランザクション処理成功時のメッセージを表示
MessageBox.Show("更新に成功しました。", "Success",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
// トランザクションがアボートされたときの処理
catch (Exception ex)
{
// トランザクション処理失敗時のメッセージを表示
MessageBox.Show("更新に失敗しました。\r\n" + ex.ToString(), "Failed",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

.NET Framework 2.0 の System.Transactions について

http://www.microsoft.com/japan/msdn/net/general/introsystemtransact.aspx

同時実行管理とクローンの作成


public class WorkerThread
{
public void DoWork(DependentTransaction dependentTransaction)
{
Thread thread = new Thread(ThreadMethod);
thread.Start(dependentTransaction);
}
public void ThreadMethod(object transaction)
{
DependentTransaction dependentTransaction;
dependentTransaction = transaction as DependentTransaction;
Debug.Assert(dependentTransaction != null);
Transaction oldTransaction = Transaction.Current;
try
{
Transaction.Current = dependentTransaction;
/* トランザクション処理を行います。 */
dependentTransaction.Complete();
}
finally
{
dependentTransaction.Dispose();
Transaction.Current = oldTransaction;
}
}
}
// クライアント コード
using(TransactionScope scope = new TransactionScope())
{
Transaction currentTransaction = Transaction.Current;
DependentTransaction dependentTransaction;
dependentTransaction =
currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
WorkerThread workerThread = new WorkerThread();
workerThread.DoWork(dependentTransaction);
/* 続いて、何かトランザクション処理を行います。 */
scope.Complete();
}
クライアント コードは、アンビエント トランザクションの設定もするトランザクショナル スコープを作成します。アンビエントトランザクションはコミット可能トランザクションですが、このトランザクションをワーカースレッドに引き渡すべきではありません。代わりに、現在のトランザクション上で DependentClone() を呼び出して、クライアントに現在の (アンビエント) トランザクションのクローンを作成させます。DependentClone() は従属トランザクション オブジェクトを生成します。ベースとなるリターン オブジェクトは DependentTransaction であり、CommittableTransaction ではありません。なお、Transaction クラスには、トランザクションの本物のクローン (CommittableTransaction (ただし該当する場合) 等) を返す、ロー メソッドの Clone() も用意されていますが、本セクションの冒頭に掲げた理由から、この危険なクローンをワーカー スレッドに引き渡さないようにしてください。

WorkerThread クラスには、新しいスレッド上で実行される ThreadMethod() が用意されています。クライアントは、従属トランザクションをスレッド メソッドパラメータとして渡す新規スレッドを開始します。問題は、完了の同期です。ワーカー スレッドの処理が終了する前に、クライアントがトランザクショナルスコープの最後に到達してしまったらどうなるでしょうか。そのとき、クライアントはどうすれば自分のトランザクションをコミットすることができるのでしょうか。

この問題を解消するために、トランザクション オブジェクトは自分が作成したすべての従属クローンをトラックします。DependentClone() メソッドは、DependentCloneOption という型名の enum 型で、cloneOption という名前のパラメータを取ります。cloneOption が DependentCloneOption.BlockCommitUntilComplete に一致している場合にクライアントがトランザクションをコミットしようとすると、そのクライアントはすべての従属トランザクションが完了するまでブロックされた状態になります。サンプル コード 15 では、トランザクション オブジェクトを破棄しようとしたクライアントが、using ステートメントの最後でブロックされています。cloneOption が DependentCloneOption.RollbackIfNotComplete の場合、クライアントがコミットしようとしていてもブロックされません。その代わり、アクティブな従属トランザクションがまだ存在しているのに、クライアントがコミットを実行しようとすると、トランザクションはアボートされ、TransactionAborted 例外が投げられます。それだけではなく、ワーカー スレッドは影響を受けないため、トランザクションを無駄に実行し続けることになります。カスタム手動同期を必要としない限り、cloneOption には常に DependentCloneOption.BlockCommitUntilComplete を設定するようにしてください。

cloneOption に DependentCloneOption.BlockCommitUntilComplete を設定していても、同時実行の問題がなくなるわけではありません。特に、次の 4 点については認識しておく必要があります。

  • ワーカー スレッドはトランザクションロールバックしたが、クライアントはコミットしようとした場合、TransactionAborted 例外が投げられます。
  • ワーカー スレッドは Complete() を一度しか呼び出すことができません。それ以上呼び出すと、InvalidOperation 例外が発生します。
  • トランザクションでは、ワーカー スレッド 1 個に対し、1 個の新しい従属クローンが作成されるようにしてください。同じ従属クローンを複数のスレッドに渡してはいけません。Complete() を実行できるのは、いずれか 1 つのスレッドだけだからです。
  • ワーカー スレッドから新しいワーカー スレッドを生成した場合は、もとのワーカー スレッドの従属クローンをベースに新しい従属クローンを作成し、それを新しいスレッドに渡すようにしてください。