次の方法で共有



January 2010

Volume 25 Number 01

多言語プログラマ - STM.NET による ACID トランザクション

Ted Neward | January 2010

このコラムでは特にプログラミング言語に注目してきましたが、言語の概念が直接変更を加えなくても他の言語に流れ込む場合があるのは興味深いことです。

このような例の 1 つは Microsoft Research 言語の Comega です (ギリシャ語のオメガ記号は米国のキーボード レイアウトの小文字の w によく似ているため、Cw という書き方をされることもあります)。Comega は、最終的に LINQ として C# 言語や Visual Basic 言語に進出することになる、データやコードを統一するいくつかの概念を紹介するだけでなく、後に Joins というライブラリになったコード (chord) と呼ばれる新しい同時実行方法も提供しました。Joins は、このコラムの執筆時点では (まだ) 製品化されていませんが、コード (chord) の同時実行概念全体をライブラリを通じて提供できるということは、任意のありふれた C# や Visual Basic (または他の .NET 言語) のプログラムでそれを利用できるということです。

このような取り組みのもう 1 つが、マイクロソフト開発者ラボ Web サイト (英語) から入手でき 2009 年 8 月号 の MSDN マガジン (英語) で取り上げられている Code Contracts 機能です。契約による設計は、Eiffel などの言語に顕著だった言語機能で、最初は Microsoft Research 言語の Spec# を通じて .NET にもたらされました。似たような種類の契約保証システムも Microsoft Research を通じてもたらされました (クライアント コードの正確性チェックを提供するためにカスタム属性と静的分析を利用した、私のお気に入りの 1 つである Fugue など)。

Code Contracts も正式な製品として、または実稼動のソフトウェアでの使用を許可するライセンス付きでは出荷されていませんが、Code Contracts が独立した言語としてではなくライブラリとして存在することは、2 つのことを含意します。1 つ目は、似たような種類の機能を用意する決意を十分に持った .NET 開発者であればだれでも、(理論上は) これをライブラリとして記述することができるということです。2 つ目は、前述の機能は (出荷されると仮定した場合)、C# や Visual Basic を含むさまざまな言語で使用できるようになる可能性があるということです。

テーマに気付かれた方もいらっしゃるかもしれません。ご想像のとおり、今月は、多言語の世界から来たもう 1 つの最近発表されたライブラリであるソフトウェア トランザクション メモリ (STM) を取り上げます。STM.NET ライブラリは開発者ラボ Web サイトを通じてダウンロードすることができますが、既に述べたその他の実装のいくつかと違って、プログラム内にリンクされたり静的分析ツールとして動作したりする独立したライブラリではありません。特筆すべきことは、これが .NET 基本クラス ライブラリ全体の代わりとなるものであり、.NET 基本クラス ライブラリ全体を補完するものであることです。

ただし、STM.NET の現在の実装は現在の Visual Studio 2010 ベータ版とあまり互換性がないので、未完成/ベータ版/CTP のソフトウェアを大切なコンピューターにインストールする場合の通常の免責事項がこの場合は二重に適用されることにご注意ください。STM.NET は Visual Studio 2008 と共にインストールする必要がありますが、Visual Studio 2008 と共にインストールする場合でも、私なら作業用のコンピューターにはインストールしません。これは Virtual PC が非常に役立つケースの 1 つです。

はじめに

STM.NET の言語的背景はいくつもの異なる場所に由来しますが、STM の概念は非常に単純でなじみのあるもので、開発者が同時実行を実現する方法 (ロックなど) に重点的に取り組まなければならないようにするのではなく、コードのどの部分が同時実行に適した特定の特性の下で実行される必要があるかをマークできるようにし、ロックは言語ツール (コンパイラやインタープリター) で必要に応じて管理するというものです。つまり、データベースの管理者やユーザーと同様に、プログラマが ACID スタイルのトランザクション セマンティクスを使用してコードをマークできるようにし、ロックの管理という単調な作業は基盤となる環境に任せます。

STM.NET は単なる同時実行管理の試みの 1 つに見えるかもしれませんが、STM の取り組みはそれよりも奥深いものを意味し、データベースの ACID トランザクションの 4 つの特性すべてをメモリ内プログラミング モデルにもたらそうとします。STM モデルは、プログラマの代わりにロックを管理するだけでなく、原子性、一貫性、分離性、および持続性も提供します。複数の実行スレッドが存在するかどうかにかかわらず、これら自体がプログラミングをはるかに単純にします。

例として、(非常によく使用されるものではありますが) 次の擬似コードについて考えてみてください。

BankTransfer(Account from, Account to, int amount) {
  from.Debit(amount);
  to.Credit(amount);
}

Credit が失敗し例外をスローするとどうなるでしょうか。口座への振り込みが行われていないのに from の口座からの引き落としが記録に残ったままである場合、ユーザーが不満を持つのは明らかです。つまり、開発者は次のようにコードを追加する必要があるということです。

BankTransfer(Account from, Account to, int amount) {
  int originalFromAmount = from.Amount;
  int originalToAmount = to.Amount;
  try {
    from.Debit(amount);
    to.Credit(amount);
  }
  catch (Exception x) {
    from.Amount = originalFromAmount;
    to.Amount = originalToAmount;
  }
}

これは一見、過剰に見えるかもしれません。ですが、Debit メソッドと Credit メソッドの具体的な実装によっては、Debit の操作が完了する前または Credit の操作が (終了はしていないが) 完了した後に例外がスローされる可能性があることを忘れないでください。つまり、BankTransfer メソッドでは、この操作で参照および使用されるすべてのデータが操作の開始時とまったく同じ状態に戻るようにする必要があるということです。

この BankTransfer が少しでも複雑になると (一度に 3 ~ 4 つのデータ項目を操作するなど)、catch ブロック内の回復コードはたちまち非常に見苦しくなります。そして、このパターンは認めたくないほど頻繁に出てきます。

注目すべきもう 1 つのポイントは分離性です。元のコードでは、別のスレッドが実行中に間違った残高を読み取り、口座のうち少なくとも 1 つに間違いを生じさせる可能性がありました。さらに、単純にロックをかけた場合、from/to のペアが必ずしも正しく並べられていないと、デッドロックに陥る可能性があります。STM は、ロックを使用することなくこれに対処します。

言語が (データベースでの BEGIN TRANSACTION/COMMIT のような) なんらかのトランザクション操作 (ロックと失敗/ロールバック ロジックを内部で処理する atomic キーワードなど) を提供していた場合、BankTransfer の例のコードは次のように単純なものになります。

BankTransfer(Account from, Account to, int amount) {
  atomic {
    from.Debit(amount);
    to.Credit(amount);
  }
}

こちらの方が心配事がはるかに少なくて済むことは認めざるをえません。

しかし、STM.NET アプローチはライブラリ ベースなので、ここまでのレベルには至りません。C# 言語ではこのレベルの構文上の柔軟性は提供されていないためです。代わりに、次のようなコードを使用することになります。

public static void Transfer(
  BankAccount from, BankAccount to, int amount) {
  Atomic.Do(() => {
    // Be optimistic, credit the beneficiary first
    to.ModifyBalance(amount);
    from.ModifyBalance(-amount);
  });
}

インサイト: これは実際のところ言語の変更である

Neward のコラムをレビューしているとき、残念ながら 1 つ基本的な誤解が目に付きました。Neward は、言語拡張を、言語の変更を必要とするものと (単に) ライブラリの変更であるものに分けようとしています。そして、STM.NET を後者 (ライブラリのみの変更) に分類しようとしています。ですが、私の考えでは、STM.NET が後者でないことにはほぼ議論の余地がありません。

ライブラリのみの拡張とは、既存の言語で完全に実装可能な拡張です。ライブラリ ベースの STM システムは確かに存在しますが、このようなシステムでは一般に、トランザクション セマンティクスを持つ必要があるデータがなんらかの特別な型 ("TransactionalInt" など) で宣言される必要があります。STM.NET はそのようなものではなく、あるトランザクションの (動的な) スコープ内でアクセスされているという理由だけで通常のデータに透過的にトランザクション セマンティクスを提供します。

これには、そのトランザクション内で実行されるコード内で行われるすべての読み取りと書き込みに変更を加えて、必要なロックの取得、シャドウ コピーの作成とシャドウ コピーへのデータ設定などを行う追加の関連する呼び出しを行うようにする必要があります。私たちは実装において、トランザクション内で実行されるまったく異なるコードを生成するように CLR の JIT コンパイラに大々的に変更を加えました。atomic キーワードは (デリゲート ベースの API を通じて提供されたとしても)、かなり根本的なレベルで言語セマンティクスを変更します。

したがって、言語が変更されたのだというのが私の主張です。C# などの .NET 言語では、言語セマンティクスは、ソース レベルの言語コンパイラと、このコンパイラによって生成される MSIL のセマンティクスに関するこのコンパイラの仮定 (CLR ランタイムがその IL をどのように実行するか) との組み合わせによって実装されます。私たちは CLR のバイトコード解釈を根本的に変更したので、私の意見では、これによって言語が変更されます。

具体的には、たとえば、CLR の JIT コンパイラが次のようなコードに遭遇したとします。

try {<br xmlns="http://www.w3.org/1999/xhtml" /> <body> <br xmlns="http://www.w3.org/1999/xhtml" />} <br xmlns="http://www.w3.org/1999/xhtml" />catch (AtomicMarkerException) {}

<body> 内のコードは (また、<body> 内のコードが呼び出すメソッド内のコードも)、トランザクション セマンティクスを保証するために動的に変更されます。これは例外処理とはまったく無関係であることを強調しておきます。これは単に atomic ブロックを特定するための処置です (try/catch 構文が、キーワードでスコープが設定されたブロックを特定するために IL に用意されている唯一のメカニズムであるため)。私たちは、ゆくゆくは明示的な "atomic" ブロックにより近いものを IL 言語で提供したいと考えています。デリゲート ベースのインターフェイスは、この、atomic ブロックの代用の観点から実装されています。

要するに、IL レベルの atomic ブロックは、どのように表現されていても、実際、ブロック内で実行されるコードのセマンティクスを根本的に変更します。STM.NET に、BCL への変更だけでなく、大幅に変更された新しい CLR ランタイムも含まれているのは、このためです。古い CLR ランタイムを使用して STM.NET の BCL を実行した場合、トランザクション セマンティクスは実現されません (実際、そもそもこれは機能するかどうかも疑問です)。

 —Dr. Dave Detlefs (マイクロソフトの共通言語ランタイム担当アーキテクト)

構文は atomic キーワードの場合ほど簡潔ではありませんが、C# では、匿名メソッドを使用して望みどおりの atomic ブロックの本体を構成するコード ブロックを表現することができるので、似たような種類のセマンティクスの下でこれを実行することができます (残念ながら、このコラムの執筆時点では、STM.NET の実験的な取り組みでサポートされているのは C# のみです。適用範囲をすべての言語に拡大できない技術的な理由があったわけではありませんが、STM.NET チームは初回リリースでは C# のみに的を絞りました)。

STM.NET の使用を開始する

まずは、ソフトウェア トランザクション メモリ対応 Microsoft .NET Framework 4 Beta 1 Version 1.0 (長ったらしい名前なので、ここからは STM.NET BCL、または単に STM.NET と略します) を開発者ラボ Web サイトからダウンロードする必要があります。また、STM.NET のドキュメントとサンプルも開発者ラボ Web サイトでダウンロードします。前者は実際の BCL と STM.NET ツールおよび補助的アセンブリで、後者には、ドキュメントとサンプル プロジェクト、および STM.NET アプリケーションを構築するための Visual Studio 2008 テンプレートが含まれます。

新しい STM.NET 対応アプリケーションの作成は、その他のアプリケーションの場合と同様に、[新しいプロジェクト] ダイアログ ボックスから始まります (図 1 参照)。TMConsoleApplication テンプレートを選択すると、いくつかの処理が行われます。行われる処理には、完全に直感的ではないものも含まれます。たとえば、このコラムの執筆時点では、STM.NET ライブラリに対して実行するには、次のように .NET アプリケーションの app.config でバージョンを少し操作する必要があります。

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
  <startup> 
    <requiredRuntime version="v4.0.20506"/> 
  </startup>
  ... 
</configuration>

図 1 TMConsoleApplication テンプレートを使用して新しいプロジェクトの作成を開始

画像: TMConsoleApplication テンプレートを使用して新しいプロジェクトの作成を開始

他の設定は用意されますが、STM.NET バージョンのランタイムに対してバインドするよう CLR 起動 shim に指示するために、requiredRuntime 値が必要です。また、TMConsoleApplication テンプレートは、古い .NET Framework 3.0 や 3.5 の CLR に付属するバージョンではなく、STM.NET がインストールされているディレクトリ内にインストールされている mscorlib アセンブリおよび System.Transactions アセンブリのバージョンに対してアセンブリをバインドします。考えてみると、STM.NET は、皆さんが記述したコード以外のものにトランザクション アクセスを提供する場合、STM.NET の mscorlib のコピーを使用する必要があるので、これが必要です。また、STM.NET が他の形式のトランザクション (Lightweight Transaction Manager (LTM) によって提供される軽量なトランザクションなど) と適切に連携するには、STM.NET バージョンの System.Transactions が必要です。

それ以外の面では、STM.NET アプリケーションは従来の .NET アプリケーションと同じです (C# で記述されて IL にコンパイルされる点、変更されていない残りの .NET アセンブリにリンクされる点など)。STM.NET アセンブリには、この 10 年間の COM+ コンポーネントや EnterpriseServices コンポーネントのように、STM.NET のトランザクション動作を処理するメソッド用のトランザクション動作を表現する拡張が他にいくつか用意される予定ですが、それについてはまたいつか説明します。

Hello, STM.NET

2009 年 9 月号の MSDN マガジンで紹介されている Axum の例 (英語) と同様に、STM.NET の出発点として従来の Hello World アプリケーションを記述するのは、実際のところ、皆さんが最初に考えるよりも困難です。トランザクションを気にせずにこのようなアプリケーションを記述すると従来の C# の Hello World とまったく同じであるというのがその主な理由です。STM.NET のトランザクション動作を利用するように記述する場合は、コンソールへのテキストの出力は実際のところ取り消すことができないメソッドである (少なくとも STM.NET に関する限り) ことを考慮する必要があります。これはつまり、Console.WriteLine ステートメントをロールバックしようとするのが困難だということです。

そのため、代わりに、STM.NET の簡単なデモンストレーションとして STM.NET ユーザー ガイドから単純な例をとってみましょう。次のように、(MyObject という) オブジェクトには、2 つのプライベート文字列、およびこの 2 つの文字列をなんらかの値に設定するためのメソッドがあります。

class MyObject {
  private string m_string1 = "1";
  private string m_string2 = "2";

  public bool Validate() {
    return (m_string1.Equals(m_string2) == false);
  }
  public void SetStrings(string s1, string s2) {
    m_string1 = s1;
    Thread.Sleep(1);   // simulates some work 
    m_string2 = s2;
  }
}

フィールドへのパラメーターの代入はそれ自体がアトミックな操作なので、ここでは同時実行に関する懸念はありません。しかし、前に示した BankAccount の例と同様に、両方が設定されるかどちらも設定されないかのいずれかにする必要があり、設定操作中に部分的な更新が行われる (1 つの文字列は設定されているが、もう 1 つの文字列は設定されていない) のは望ましくありません。文字列を何度もやみくもに設定するために 2 つのスレッドを生成し、MyObject インスタンスのコンテンツを検証して Validate が false を返した場合は違反をレポートするために 3 つ目のスレッドを生成します (図 2 参照)。

図 2 MyObject へのアトミックな更新を手動で検証

[AtomicNotSupported]
static void Main(string[] args) {
  MyObject obj = new MyObject();
  int completionCounter = 0; int iterations = 1000;
  bool violations = false;

  Thread t1 = new Thread(new ThreadStart(delegate {
    for (int i = 0; i < iterations; i++)
      obj.SetStrings("Hello", "World");
    completionCounter++;
  }));

  Thread t2 = new Thread(new ThreadStart(delegate {
    for (int i = 0; i < iterations; i++)
      obj.SetStrings("World", "Hello");
    completionCounter++;
  }));

  Thread t3 = new Thread(new ThreadStart(delegate {
    while (completionCounter < 2) {
      if (!obj.Validate()) {
        Console.WriteLine("Violation!");
        violations = true;
      }
    }
  }));

  t1.Start(); t2.Start(); t3.Start();
  while (completionCounter < 2)
    Thread.Sleep(1000);

  Console.WriteLine("Violations: " + violations);
...

この例は、obj の 2 つの文字列が同じ値に設定されている場合は検証が失敗し、Thread t1 の SetStrings("Hello", "World") が部分的に更新されている (1 つ目の "Hello" が t2 によって設定された 2 つ目の "Hello" と同じ値のままである) ことが通知されるように構築されています。

SetStrings の実装をちらっと見ただけで、このコードが決してスレッド セーフではないことがわかります。途中でスレッドの切り替えが行われると (Thread.Sleep が呼び出されることを考えると、これは起こりうることです。Thread.Sleep が呼び出されると、現在実行中のスレッドはタイム スライスを中断します)、別のスレッドが容易に SetStrings の途中に再び割り込んで MyObject インスタンスを無効な状態にすることができてしまいます。これを十分な回数繰り返し実行すると、違反の通知が表示されるようになります (私のノート PC では、違反の通知が表示されるまでには 2 回実行する必要がありました。これは、エラーが発生することなく 1 回実行できたからといってコードに同時実行バグがないとは限らないことを証明しています)。

STM.NET を使用するようにこれに変更を加えるには、MyObject クラスに小さな変更を加えるだけで済みます (図 3 参照)。

図 3 STM.NET による MyObject の検証

class MyObject {
  private string m_string1 = "1";
  private string m_string2 = "2";

  public bool Validate() {
    bool result = false;
    Atomic.Do(() => { 
      result = (m_string1.Equals(m_string2) == false);
    });
    return result;
  }

  public void SetStrings(string s1, string s2) {
    Atomic.Do(() => {
      m_string1 = s1;
      Thread.Sleep(1); // simulates some work 
      m_string2 = s2;
    });
  }
}

ご覧のとおり、必要な唯一の変更は、Atomic.Do 操作を使用して Validate と SetStrings の本体をアトミック メソッド内にラップすることでした。これで、実行しても違反の通知は表示されなくなります。

トランザクション アフィニティ

目ざとい読者の方は、図 2 の Main メソッドの先頭にある [AtomicNotSupported] 属性にお気付きでしょう。そしておそらく、この属性の目的は何なのか疑問に思ったり、COM+ のころの属性と同じ目的を果たすのだろうかと思ったりしたでしょう。実は、まったくそのとおりです。STM.NET 環境は、Atomic ブロック内で呼び出されたメソッドに必要で望ましいサポートを提供できるように、こうしたメソッドがトランザクションに適しているかどうかを知るための援助を必要とします。

現在の STM.NET リリースでは、このような属性が 3 つ用意されています。これらを以下に示します。

  • AtomicSupported: 当該のアセンブリ、メソッド、フィールド、またはデリゲートは、トランザクション動作をサポートし、atomic ブロックの内部または外部で問題なく使用することができます。
  • AtomicNotSupported: 当該のアセンブリ、メソッド、フィールド、またはデリゲートは、トランザクション動作をサポートしていないので、atomic ブロックの内部で使用するべきではありません。
  • AtomicRequired: 当該のアセンブリ、メソッド、フィールド、またはデリゲートは、トランザクション動作をサポートするだけでなく、atomic ブロック内でのみ使用する必要があります (これにより、この項目の使用が常にトランザクション セマンティクスの下で行われることが保証されます)。

厳密に言うと、AtomicUnchecked という 4 つ目の属性もあります。この属性は、この項目をチェックするべきではないことを STM.NET に通知するだけです。この属性は、コードのチェックを完全に回避するための "非常口" となることを目的としています。

次の (単純な) コードを実行すると、AtomicNotSupported 属性が指定されているため、STM.NET システムは AtomicContractViolationException をスローします。

[AtomicNotSupported]
static void Main(string[] args) {
  Atomic.Do( () => {
    Console.WriteLine("Howdy, world!");
  });

  System.Console.WriteLine("Simulation done");
}

System.Console.WriteLine メソッドは AtomicSupported でマークされていないので、Atomic.Do メソッドは、atomic ブロック内に呼び出しを見つけると例外をスローします。このちょっとしたセキュリティによって、トランザクションに適したメソッドのみが atomic ブロック内で実行されることが保証され、この追加のちょっとした安全性とセキュリティがコードに提供されます。

Hello, STM.NET (その 2)

どうしても従来の Hello World を記述する必要がある場合はどうすればよいでしょうか。他の 2 つのトランザクション操作に加えてどうしてもコンソールに行を出力する (または、ファイルへの書き込みを行ったり、他のなんらかの非トランザクション動作を実行したりする) 必要があるが、この他の 2 つの操作の両方が成功した場合にのみ出力する場合はどうすればよいでしょうか。STM.NET には、この状況に対処する方法が 3 つ用意されています。

1 つ目は、Atomic.DoAfterCommit に渡されるブロック内にコードを配置することによって、トランザクションの外で (また、トランザクションがコミットされた場合にのみ) 非トランザクション操作を実行するという方法です。このブロック内のコードは通常、トランザクション内から生成または変更されたデータを使用するので、DoAfterCommit は、トランザクション内から唯一のパラメーターとしてコード ブロックに渡されるコンテキスト パラメーターを受け取ります。

2 つ目は、Atomic.DoWithCompensation を呼び出すことによって、トランザクションが最終的に失敗した場合に実行される埋め合わせ用の動作を作成するという方法です。Atomic.DoWithCompensation は (も)、トランザクション内からコミット用または埋め合わせ用のコード ブロック (のうち適切な方) にデータをマーシャリングするために、コンテキスト パラメーターを受け取ります。

3 つ目は、STM.NET トランザクション システムに関与する方法を理解しているトランザクション リソース マネージャー (RM) を作成するという徹底的な方法です。これは実際のところ、皆さんが思っているほど難しくありません。STM.NET の TransactionalOperation クラスを継承し、このクラスに用意されている OnCommit メソッドと OnAbort メソッドのそれぞれをオーバーライドして適切な動作が提供されるようにすればよいだけです。この新しい RM 型を使用する際は、これに対する操作の開始時に OnOperation を呼び出します (リソースを効果的に STM.NET トランザクションに参加させます)。そして、周囲の操作が失敗した場合はそこで FailOperation を呼び出します。

したがって、テキスト ベースのストリームへの書き込みをトランザクションとして行う必要がある場合は、テキストの追加を行う図 4 のようなリソース マネージャーを記述することができます。これを使用すると、atomic ブロック内で TxAppender を通じてテキスト ストリームへの書き込みを行うことができます (というよりむしろ、要求されます。[AtomicRequired] 属性があるためです。図 5 参照)。

図 4 トランザクション リソース マネージャー

public class TxAppender : TransactionalOperation {
  private TextWriter m_tw;
  private List<string> m_lines;

  public TxAppender(TextWriter tw) : base() {
    m_tw = tw;
    m_lines = new List<string>();
  }

  // This is the only supported public method
  [AtomicRequired]
  public void Append(string line) {
    OnOperation();

    try {
      m_lines.Add(line);
    }
    catch (Exception e) {
      FailOperation();
      throw e;
    }
  }

  protected override void OnCommit() {
    foreach (string line in m_lines) {
      m_tw.WriteLine(line);
    }
    m_lines = new List<string>();
  }

  protected override void OnAbort() {
    m_lines.Clear();
  }
}

図 5 TxAppender の使用

public static void Test13() {
  TxAppender tracer = 
    new TxAppender(Console.Out);
  Console.WriteLine(
    "Before transactions. m_balance= " + 
    m_balance);

  Atomic.Do(delegate() {
    tracer.Append("Append 1:  " + m_balance);
    m_balance = m_balance + 1;
    tracer.Append("Append 2:  " + m_balance);
  });
            
  Console.WriteLine(
    "After transactions. m_balance= " 
    + m_balance);

  Atomic.Do(delegate() {
    tracer.Append("Append 1:  " + m_balance);
    m_balance = m_balance + 1;
    tracer.Append("Append 2:  " + m_balance);
  });

  Console.WriteLine(
    "After transactions. m_balance= " 
    + m_balance);
}

これは明らかにより手間のかかる方法であり、特定のシナリオにのみ適しています。メディアの種類によってはこれは失敗することがありますが、ほとんどの場合、実際の元に戻すことができない動作がすべて OnCommit メソッドにゆだねられているのであれば、これはメモリ内のトランザクション ニーズのほとんどを満たすでしょう。

STM.NET を使用する

STM システムの使用にはちょっとした慣れが必要ですが、慣れてしまえば、STM システムなしで作業するのが不自由に思えるでしょう。STM.NET の使用によってコーディングが簡単になる可能性のある場所のいくつかについて考えてみましょう。

STM.NET は、他のトランザクションされたリソースと連携する際、既存のトランザクションされたシステムに迅速かつ容易にプラグインし、Atomic.Do のみをシステムにおけるトランザクションされたコードの源とします。STM.NET の例では、TraditionalTransactions サンプルで、メッセージを MSMQ の専用キューに登録し、Atomic ブロックが失敗した場合はメッセージがキューに登録されないことを明らかにすることによって、これが示されています。これがおそらく最も理解しやすい使い方です。

ダイアログ ボックスで (特に、複数のステップから成るウィザード プロセスや設定ダイアログ ボックスでは)、ユーザーが [Cancel] (キャンセル) ボタンをクリックしたときに設定やダイアログ ボックスのデータ メンバーへの変更をロールバックする機能は非常に貴重です。

NUnit、MSTest などのシステムの単体テストでは、(適切に記述されていれば) あるテストの結果が次のテストに漏えいしないように多大な努力が払われます。STM.NET が正式版の状態に到達したら、NUnit と MSTest のテスト ケース実行コードをリファクターして、STM トランザクションを使用してテスト結果を互いに分離し、各テスト メソッドの最後にロールバックを生成して、テストによって生成されていた可能性のあるあらゆる変更をなくすようにすることができます。さらに、警告なしでテスト環境の外部にあるメディア (ディスクやデータベースなど) にテスト結果が漏えいされるのではなく、AtomicUnsupported メソッドを呼び出すすべてのテストにはテスト実行時にエラーとしてフラグが付けられます。

STM.NET は、ドメイン オブジェクト プロパティの実装でも使用することができます。ほとんどのドメイン オブジェクトのプロパティは、フィールドに値を代入するかそのフィールドの値を返すというかなり単純なものですが、複数のステップから成るアルゴリズムを持つ、より複雑なプロパティがある場合、複数のスレッドで部分的な更新 (別のスレッドが値の設定中にプロパティを呼び出した場合) や擬似更新 (別のスレッドが値の設定中にプロパティを呼び出し、最初の更新がなんらかの検証エラーによって最終的に破棄された場合) が行われる危険性があります。

さらに興味深いことに、マイクロソフトの外部の研究者たちは、トランザクションの適用範囲をハードウェアにまで拡大することを検討しているので、ゆくゆくは、オブジェクトのフィールドやローカル変数の更新はハードウェア レベルでメモリ チップ自体によって保護されたトランザクションとなり、トランザクションが現在の方法と比べて大幅に高速になる可能性があります。

ですが、Axum の場合と同様に、マイクロソフトは、このテクノロジが追求および製品化する価値のあるものかどうかを皆さんからのフィードバックを頼りに判断しますので、この概念を魅力的または興味深いと思った場合や、皆さんのコーディング プラクティスにとっての重要な要素がこの概念に欠けていると感じた場合は、遠慮なくお知らせください。                                  

Ted Neward は、.NET および Java のエンタープライズ システムを専門とする独立系の会社 Neward & Associates の社長です。多数の著書があり、Microsoft MVP アーキテクト、INETA の講演者、PluralSight のインストラクターでもあります。Ted の連絡先は、ted@tedneward.com (英語のみ) です。また、blogs.tedneward.com (英語) にブログを公開しています。

この記事のレビューに協力してくれた技術スタッフの Dave Detlefs と Dana Groff に心より感謝いたします。