63

メインスレッドの構成が完了した後、つまり「フリーズ」した後に読み取り専用にしたいクラスを設計しています。Eric Lippertは、これをアイスキャンデーの不変性と呼んでいます。フリーズした後は、複数のスレッドから同時にアクセスして読み取ることができます。

私の質問は、これをスレッドセーフな方法で、現実的に効率的な方法で、つまり不必要に賢くしようとせずに書く方法です。

試行1:

public class Foobar
{
   private Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   // Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }

   public Object ReadSomething()
   {
      return it;
   }
}

Eric Lippertは、この投稿でこれで問題ないと示唆しているようです。書き込みにはリリースセマンティクスがあることは知っていますが、これは順序付けにのみ関係することを理解している限り、必ずしもすべてのスレッドが書き込みの直後に値を確認することを意味するわけではありません。誰かがこれを確認できますか?これは、このソリューションがスレッドセーフではないことを意味します(もちろん、これが唯一の理由ではない可能性があります)。

試行2:

上記ですがInterlocked.Exchange、値が実際に公開されていることを確認するために使用します。

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (_isFrozen == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

ここでの利点は、読み取りのたびにオーバーヘッドが発生することなく、値が確実に公開されることです。Interlockedメソッドは完全なメモリバリアを使用するため、_isFrozenへの書き込みの前に読み取りが移動されない場合、これはスレッドセーフであると思います。ただし、コンパイラが何をするかは誰にもわかりません(C#仕様のセクション3.10によると、かなり多くのように思われます)。したがって、これがスレッドセーフかどうかはわかりません。

試行3:

また、を使用して読み取りを行いますInterlocked

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

間違いなくスレッドセーフですが、読み取りごとに比較交換を行う必要があるのは少し無駄に思えます。私はこのオーバーヘッドがおそらく最小限であることを知っていますが、私は適度に効率的な方法を探しています(おそらくこれはそれですが)。

試行4:

使用volatile

public class Foobar
{
   private volatile Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }
}

しかし、ジョー・ダフィーは「さよなら揮発性」と宣言したので、これを解決策とは考えません。

試行5:

すべてをロックし、少しやり過ぎのようです:

public class Foobar
{
   private readonly Object _syncRoot = new Object();
   private Boolean _isFrozen;

   public void Freeze() { lock(_syncRoot) _isFrozen = true; }

   public void WriteValue(Object val)
   {
      lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
         if (_isFrozen)
            throw new InvalidOperationException();

      // write ...
   }
}

また、間違いなくスレッドセーフのようですが、上記のインターロックアプローチを使用するよりもオーバーヘッドが大きいため、これよりも試行3を優先します。

そして、私は少なくとももう少し思いつくことができます(私はもっとたくさんあると確信しています):

試行6:とを使用Thread.VolatileWriteしますThread.VolatileReadが、これらはおそらく少し重い面です。

試行7:使用Thread.MemoryBarrier、少し内部的すぎるようです。

試行8:不変のコピーを作成します-これを実行したくない

要約:

  • どの試みを使用しますか、またその理由(または、まったく異なる場合はどのように実行しますか)?(つまり、値を一度公開して同時に読み取るための最良の方法は何ですか?ただし、過度に「賢く」なることなく、適度に効率的です)。
  • .NETのメモリモデルの書き込みの「リリース」セマンティクスは、他のすべてのスレッドが更新(キャッシュコヒーレンシなど)を参照することを意味しますか?私は一般的にこれについてあまり考えたくないのですが、理解しておくのはいいことです。

編集:

おそらく私の質問は明確ではありませんでしたが、私は特に上記の試みが良いか悪いかについての理由を探しています。ここでは、同時読み取りの前に書き込みを行ってからフリーズする1つのライターのシナリオについて話していることに注意してください。試行1は問題ないと思いますが、その理由を正確に知りたいと思います(たとえば、読み取りを何らかの方法で最適化できるかどうか疑問に思います)。私はこれが良い設計慣行であるかどうかについては気にしませんが、それの実際の糸の側面についてはもっと気にします。


質問への回答に感謝しますが、私はこれを自分で回答としてマークすることにしました。これは、与えられた回答が私の質問に完全に答えていないように感じサイトにアクセスした人にマークを付けた印象を与えたくないためです。バウンティの有効期限が切れたために自動的にそのようにマークされたという理由だけで、答えは正しいです。さらに、投票数が最も多い回答が圧倒的に投票されたとは思いません。自動的に回答としてマークするのに十分ではありません。

私はまだ#1が正しいことを試みることに傾いています、しかし、私はいくつかの権威ある答えが好きでした。x86には強力なモデルがあることは理解していますが、特定のアーキテクチャー用にコーディングしたくない(そしてコーディングすべきではありません)。結局のところ、それは.NETの優れた点の1つです。

答えがわからない場合は、ロックの方法の1つを選択してください。おそらく、ここに示す最適化を使用して、ロックに関する多くの競合を回避します。

4

12 に答える 12

25

少し話題から外れているかもしれませんが、好奇心からです:)「本当の」不変性を使ってみませんか?たとえば、Freeze()に不変のコピーを返させ(「書き込みメソッド」や内部状態を変更するその他の可能性なし)、元のオブジェクトの代わりにこのコピーを使用します。代わりに、状態を変更せずに、書き込み操作ごとに(状態が変更された)新しいコピーを返すこともできます(文字列クラスはこれで機能します)。「真の不変性」は本質的にスレッドセーフです。

于 2012-12-12T17:56:17.143 に答える
8

私は試行 5 に投票し、lock(this) 実装を使用します。

これは、この作業を行うための最も信頼できる手段です。リーダー/ライター ロックを使用することもできますが、効果はほとんどありません。通常のロックを使用してください。

必要に応じて、最初にチェック_isFrozenしてからロックすることで、「凍結」パフォーマンスを改善できます。

void Freeze() { lock (this) _isFrozen = true; }
object ReadValue()
{
    if (_isFrozen)
        return Read();
    else
        lock (this) return Read();
}
void WriteValue(object value)
{
    lock (this)
    {
        if (_isFrozen) throw new InvalidOperationException();
        Write(value);
    }
}
于 2012-12-03T21:50:25.333 に答える
5

他のスレッドに表示する前にオブジェクトを実際に作成、入力、およびフリーズする場合は、スレッドセーフに対処するために特別なことは必要ありません (.NET の強力なメモリ モデルが既に保証されています)。したがって、ソリューション 1 は次のとおりです。有効。

ただし、凍結されていないオブジェクトを別のスレッドに渡す場合 (または、ユーザーがどのように使用するかを知らずにクラスを作成するだけの場合) は、バージョンを使用して、新しい完全に不変のインスタンスを返すソリューションの方がおそらく優れています。この場合、可変インスタンスは StringBuilder に似ており、不変インスタンスは文字列に似ています。追加の保証が必要な場合、変更可能なインスタンスはその作成者スレッドをチェックし、他のスレッドから使​​用されている場合は例外をスローする場合があります (すべてのメソッドで...部分読み取りの可能性を回避するため)。

于 2013-05-06T16:23:02.573 に答える
2

おそらく私の質問は明確ではありませんでしたが、私は特に上記の試みが良いか悪いかについての理由を探しています。ここでは、同時読み取りの前に書き込みを行ってからフリーズする1つのライターのシナリオについて話していることに注意してください。試行1は問題ないと思いますが、その理由を正確に知りたいと思います(たとえば、読み取りを何らかの方法で最適化できるかどうか疑問に思います)。私はこれが良い設計慣行であるかどうかについては気にしませんが、それの実際の糸の側面についてはもっと気にします。

さて、私はあなたが何をしていて、応答で何を探しているのかをよりよく理解しました。最初にあなたのそれぞれの試みに取り組むことによって、ロックの使用を促進する私の以前の答えについて詳しく説明させてください。

試行1:

どの形式の同期プリミティブも持たない単純なクラスを使用するアプローチは、この例では完全に実行可能です。'authoring'スレッドは、変更状態のときにこのクラスにアクセスできる唯一のスレッドであるため、これは安全であるはずです。クラスが「凍結」される前に別のスレッドがアクセスする可能性がある場合にのみ、同期を提供する必要があります。基本的に、スレッドがこれまでに見たことのないもののキャッシュを持つことはできません。

このリストの内部状態のキャッシュされたコピーを持つスレッドとは別に、懸念すべきもう1つの並行性の問題があります。オーサリングスレッドによる書き込みの並べ替えを検討する必要があります。サンプルソリューションには、これに対処するための十分なコードがありませんが、この「凍結」リストを別のスレッドに渡すプロセスが問題の核心です。Interlocked.Exchangeを使用していますか、それとも揮発性の状態に書き込んでいますか?

変異している間に別のスレッドがインスタンスを認識していないという保証がないという理由だけで、これは最善のアプローチではないことを今でも主張しています。

試行2:

試行2は使用しないでください。メンバーへのアトミック書き込みを使用している場合は、アトミック読み取りも使用する必要があります。読み取りと書き込みの両方がアトミックでないと、何も得られないため、一方を他方なしで推奨することは決してありません。アトミック読み取りおよび書き込みの正しいアプリケーションは、「Attempt3」です。

試行3:

これにより、スレッドがフリーズしたリストを変更しようとした場合に例外がスローされることが保証されます。ただし、読み取りがフリーズされたインスタンスでのみ受け入れられるという主張はありません。_isFrozenこれ、IMHOは、アトミックおよび非アトミックアクセサーを使用して変数にアクセスするのと同じくらい悪いです。書き込みを保護することが重要であると言う場合は、常に読み取りを保護する必要があります。他のないものはただ「奇妙」です。

gaurdsが書き込むが読み取らないコードを書くことに対する私自身の気持ちを見落とすことは、特定の用途を考えると許容できるアプローチです。私には1人のライターがいて、書き、フリーズしてから、読者が利用できるようにします。このシナリオでは、コードは正しく機能します。_isFrozenクラスを別のスレッドに渡す前に、必要なメモリバリアを提供するために、のセットに対する不可分操作に依存します。

一言で言えば、このアプローチは機能しますが、スレッドにフリーズされていないインスタンスがある場合、それは壊れます。

試行4:

本質的には、これは試行3(1人のライターが与えられた場合)とほぼ同じですが、大きな違いが1つあります。この例で_isFrozenは、リーダーをチェックインすると、すべてのアクセスにメモリバリアが必要になります。リストがフリーズすると、これは不要なオーバーヘッドになります。

_isFrozenそれでも、これには試行3と同じ問題があり、読み取り中の状態についてアサーションが作成されないため、使用例でのパフォーマンスは同じである必要があります。

試行5:

私が言ったように、これは私の他の答えに現れるように読むための修正を考えると私の好みです。

試行6:

基本的に#4と同じです。

試行7:

を使用して特定のニーズを解決できますThread.MemoryBarrier。基本的に、試行1のコードを使用して、インスタンスを作成し、呼び出しFreeze()、を追加してThread.MemoryBarrierから、インスタンスを共有します(またはロック内で共有します)。これも、限られたユースケースでのみうまく機能するはずです。

試行8:

これについてもっと知らなければ、私はコピーの費用についてアドバイスすることはできません。

概要

繰り返しになりますが、スレッドが保証されているか、まったく保証されていないクラスを使用することをお勧めします。「部分的に」スレッドセーフなクラスを作成することは、IMOにとって危険です。

有名なジェダイマスターの言葉で:

するかしないかのどちらかは試してみません。

スレッドセーフについても同じことが言えます。クラスはスレッドセーフであるかどうかのどちらかである必要があります。このアプローチを採用すると、試行5の拡張を使用するか、試行7を使用することになります。選択肢があれば、#7をお勧めすることはありません。

したがって、私の推奨事項は、完全にスレッドセーフなバージョンの背後にしっかりと立っています。2つの間のパフォーマンスコストは非常に小さいため、ほとんど存在しません。ライターが1つしかないという使用シナリオのために、リーダースレッドがロックに達することはありません。それでも、そうする場合でも、適切な動作は確実です。したがって、コードが時間の経過とともに変化し、フリーズする前にインスタンスが突然共有されるため、プログラムをクラッシュさせる競合状態に陥ることはありません。スレッドセーフであろうとなかろうと、ハーフインしないでください。そうしないと、いつか厄介な驚きに陥ってしまいます。

私の好みは、複数のスレッドで共有されるすべてのクラスが2つのタイプのいずれかであるということです。

  1. 完全に不変。
  2. 完全にスレッドセーフ。

アイスキャンデーのリストは設計上不変ではないため、#1には適合しません。したがって、スレッド間でオブジェクトを共有する場合は、#2に適合する必要があります。

うまくいけば、このすべての怒りが私の推論をさらに説明します:)

_syncRoot

_syncRoot多くの人が、私がロックの実装での使用をスキップしたことに気づきました。_syncRootを使用する理由は有効ですが、必ずしも必要なわけではありません。単一のライターがある使用例では、のlock(this)別のヒープ割り当てを追加しなくても、の使用で十分です_syncRoot

于 2012-12-13T20:55:18.567 に答える
2

#1 - リーダーはスレッドセーフではありません - 問題はライターではなくリーダー側にあると思います (コードは示されていません)
#2 - リーダーはスレッドセーフではありません - #1 と同じ
ですCPU キャッシュは同期しています)

試行 3:

また、インターロックを使用して読み取りを行います。

public class Foobar {

  private object _syncRoot = new object();
  private int _isFrozen = 0; // perf compiler warning, but training code, so show defaults

  // Why Exchange to 1 then throw away result.  Best to just increment.
  //public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
  public void Freeze() { Interlocked.Increment(ref _isFrozen); }

  public void WriteValue(Object val) {
       // if this core can see _isFrozen then no special lock or sync needed
       if (_isFrozen != 0)
           throw new InvalidOperationException();

       lock(_syncRoot) {
           if (_isFrozen != 0)
               throw new InvalidOperationException(); // the 'throw' is 100x-1000x more costly than the lock, just eat it
           _val = val;
       }

  }

  public object Read() {
       // frozen is one-way, if one-way state has been published 
       // to my local CPU cache then just read _val.  
       // There are very strange corner cases when _isFrozen and _val fields are in 
       // different cache lines, but should be nearly impossible to hit unless
       // dealing with very large structs (make it more likely to cross 
       // 4k cache line).
       if (_isFrozen != 0) 
           return _val;

       // else
       lock(_syncRoot) { // _isFrozen is 0 here
           if (_isFrozen != 0) // if _isFrozen is 1 here we just collided with writer using lock on other thread, or our CPU cache was out of sync and lock() forced the dirty cache line to be read from main memory
              return _val;

           throw new InvalidOperationException(); // throw is 100x-1000x more expensive than lock, eat the cost of lock
       }          
  }

}

Joe Duffy の「volatile is dead」に関する投稿は、彼の次世代 CLR/OS アーキテクチャと ARM 上の CLR のコンテキストにあると思います。マルチコア x64/x86 を実行している私たちは、揮発性で問題ないと思います。パフォーマンスが主な関心事である場合は、上記のコードを測定して volatile と比較することをお勧めします。

回答を投稿している他の人々とは異なり、多くの読者がいる場合 (3 つ以上のスレッドが同時に同じオブジェクトを読み取る可能性が高い)、lock() に直接ジャンプすることはありません。しかし、サンプルでは、​​衝突が発生したときにパフォーマンスに敏感な質問と例外を混在させていますが、これはあまり意味がありません。例外を使用している場合は、他の上位レベルの構成も使用できます。

完全な安全性が必要であるが、多数の同時リーダーを最適化する必要がある場合は、lock()/Monitor を ReaderWriterLockSlim に変更します。

.NET には、公開値を処理するための新しいプリミティブがあります。Rxを見てください。場合によっては、非常に高速でロックレスになる可能性があります(上記と同様の最適化を使用していると思います)。

複数回書き込まれるが、保持される値は 1 つだけの場合 - Rx では「new ReplaySubject(bufferSize: 1)」です。試してみると、その速さに驚くかもしれません。同時に、このレベルの詳細を学ぼうとするあなたの試みに拍手を送ります。

ロックレスになりたい場合は、Thread.MemoryBarrier() に対する嫌悪感を克服してください。それは非常に重要です。しかし、Joe Duffy によって説明されているように、volatile と同じ落とし穴があります。これは、コンパイラと CPU へのヒントとして設計され、メモリ読み取りの並べ替えを防止します (CPU の観点からは長い時間がかかるため、メモリ読み取りがない場合は積極的に並べ替えられます)。ヒントあり)。この並べ替えを関数の自動インライン化などの CLR 構造と組み合わせると、メモリおよびレジスタ レベルで非常に驚くべき動作が見られます。MemoryBarrier() は、CPU と CLR がほとんどの場合に使用するシングルスレッド メモリ アクセスの仮定を無効にするだけです。

于 2012-12-13T11:27:52.073 に答える
2

試行 2 は、強力なメモリ モデルを備えた x86 およびその他のプロセッサでスレッド セーフですが、消費されるコード内で効率的にスレッド セーフを実行する方法がないため、消費者の問題をスレッド セーフにする方法があります。検討:

if(!foo.frozen)
{
    foo.apropery = "avalue";
}

プロパティのスレッドセーフとのセッターfrozenのガードコードは、apropery完全にスレッドセーフであっても競合状態が発生するため、実際には問題になりません。代わりに、次のように書きます

lock(foo)
{
    if(!foo.frozen)
    {
        foo.apropery = "avalue";
    }
}

どちらのプロパティも本質的にスレッドセーフではありません。

于 2012-12-03T22:39:28.363 に答える
1

次のアプローチがどのように機能するかについては、コストの観点からはわかりませんが、少し異なります。複数のスレッドが同時に値を書き込もうとしている場合にのみ、ロックが発生します。フリーズすると、それ以降のすべての呼び出しで直接例外が発生します。

試行 9:

public class Foobar
{
    private readonly Object _syncRoot = new Object();
    private object _val;
    private Boolean _isFrozen;

    private Action<object> WriteValInternal;

    public void Freeze() { _isFrozen = true; }

    public Foobar()
    {
        WriteValInternal = BeforeFreeze;
    }

    private void BeforeFreeze(object val)
    {
        lock (_syncRoot)
        {
            if (_isFrozen == false)
            {
                //Write the values....
                _val = val;
                //...
                //...
                //...
                //and then modify the write value function
                WriteValInternal = AfterFreeze;
                Freeze();
            }
            else
            {
                throw new InvalidOperationException();
            }
        }
    }

    private void AfterFreeze(object val)
    {
        throw new InvalidOperationException();
    }

    public void WriteValue(Object val)
    {
        WriteValInternal(val);
    }

    public Object ReadSomething()
    {
        return _val;
    }
}
于 2012-12-06T23:49:20.227 に答える
1

一般に、各ミュータブル オブジェクトには、明確に定義された「所有者」が正確に 1 つ必要です。共有オブジェクトは不変でなければなりません。アイスキャンディーは、フリーズするまで複数のスレッドからアクセスできないようにする必要があります。

個人的には、露出した「フリーズ」メソッドを使用したアイスキャンデー免疫の形態は好きではありません。AsMutableよりクリーンなアプローチは、およびメソッドを使用することだと思いますAsImmutable(それぞれが、適切な場合にオブジェクトを変更せずに返すだけです)。このようなアプローチにより、不変性に関するより堅牢な約束が可能になります。たとえば、「共有されていない変更可能なオブジェクト」が、そのAsImmutableメンバーが呼び出されている間に変更されている場合 (オブジェクトが「共有されていない」ことに反する動作)、コピー内のデータの状態は不確定になる可能性がありますが、返されたものは何でも不変でしょう。対照的に、あるスレッドがオブジェクトを凍結し、別のスレッドがそのオブジェクトに書き込みを行っている間にそのオブジェクトが不変であると想定した場合、「不変」オブジェクトは、凍結されて値が読み取られた後に変更される可能性があります。

編集

詳細な説明に基づいて、オブジェクトに書き込むコードをモニターロック内で実行し、フリーズルーチンを次のようにすることをお勧めします。

public Thingie Freeze(void) // 問題のオブジェクトを返します
{
  if (isFrozen) // プライベート フィールド
    これを返します。
  そうしないと
    DoFreeze() を返します。
}

Thingie DoFreeze(void)
{
  if (Monitor.TryEnter(何でも))
  {
    isFrozen = true;
    これを返します。
  }
  else if (isFrozen)
    これを返します。
  そうしないと
    throw new InvalidOperationException("ライターが使用中のオブジェクト");
}

このFreezeメソッドは、任意の数のスレッドから何度でも呼び出すことができます。インライン化するのに十分なほど短くする必要があり (プロファイルは作成していませんが)、実行にほとんど時間がかからないはずです。スレッド内のオブジェクトの最初のアクセスがFreezeメソッド経由である場合、妥当なメモリ モデルの下で適切な可視性が保証されるはずです (スレッドがオブジェクトを作成して最初に凍結したスレッドによって実行されたオブジェクトへの更新をスレッドが認識しなかったとしても、TryEnterメモリバリアを保証する を実行し、失敗した後、オブジェクトが凍結されていることに気づき、それを返します。

オブジェクトを書き込もうとしているコードが最初にロックを取得すると、凍結されたオブジェクトに書き込もうとするとデッドロックする可能性があります。そのようなコードで例外をスローしたい場合はTryEnter、ロックを取得できない場合に例外を使用してスローします。

ロックに使用するオブジェクトは、凍結するオブジェクトが排他的に保持するものである必要があります。凍結されるオブジェクトが純粋にプライベートな参照を保持していない場合は、ロックするthisか、純粋にロックする目的でプライベート オブジェクトを作成することができます。クリーンアップせずに「入力された」モニターロックを放棄しても安全であることに注意してください。ロックへの参照が存在しない場合、ロックが放棄されたときにロックが入力されたかどうかを気にする (または尋ねることさえできる) 方法がないため、GC は単にそれらを忘れます。

于 2012-12-03T23:17:52.240 に答える
1

ものは構築されて書き込まれ、その後永久に凍結され、複数回読み取られますか?

それとも、凍結と解凍と再凍結を何度も繰り返しますか?

前者の場合、おそらく「フリーズ」チェックは、ライター メソッドではなくリーダー メソッドで行う必要があります (フリーズする前に読み取らないようにするため)。

または、後者の場合、注意する必要があるユースケースは次のとおりです。

  • メインスレッドはライターメソッドを呼び出し、それが凍結されていないことを発見し、書き込みを開始します
  • 書き込みが完了する前に、他の (メイン) スレッドがまだ書き込みを行っている間に、誰かがオブジェクトをフリーズしてから読み取りを試みます。

後者の場合、Google は、興味深いと思われる複数のリーダーと単一のライターの多くの結果を表示します。

于 2012-12-03T21:49:23.377 に答える
0

レイジーをチェックアウトしましたか

http://msdn.microsoft.com/en-us/library/dd642331.aspx

ThreadLocalを使用します

http://msdn.microsoft.com/en-us/library/dd642243.aspx

そして実際にさらに見てみると、Freezableクラスがあります...

http://msdn.microsoft.com/en-us/library/vstudio/ms602734(v=vs.100).aspx

于 2012-12-13T19:37:13.640 に答える
0

POST Sharp を使用してこれを達成することができます

1つのインターフェースを取る

public interface IPseudoImmutable
{

    bool IsFrozen { get; }


    bool Freeze();
}

次に、このように InstanceLevelAspect から属性を派生させます

 /// <summary>
/// implement by divyang
/// </summary>
[Serializable]
[IntroduceInterface(typeof(IPseudoImmutable),
    AncestorOverrideAction = InterfaceOverrideAction.Ignore, OverrideAction = InterfaceOverrideAction.Fail)]
public class PseudoImmutableAttribute : InstanceLevelAspect, IPseudoImmutable
{

    private volatile bool isFrozen;

    #region "IPseudoImmutable"


    [IntroduceMember]
    public bool IsFrozen
    {
        get
        {
            return this.isFrozen;
        }
    }


    [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.Fail)]
    public bool Freeze()
    {
        if (!this.isFrozen)
        {
            this.isFrozen = true;
        }

        return this.IsFrozen;
    }

    #endregion


    [OnLocationSetValueAdvice]
    [MulticastPointcut(Targets = MulticastTargets.Property | MulticastTargets.Field)]
    public void OnValueChange(LocationInterceptionArgs args)
    {
        if (!this.IsFrozen)
        {
            args.ProceedSetValue();
        }
    }
}


public class ImmutableException : Exception
{
    /// <summary>
    /// The location name.
    /// </summary>
    private readonly string locationName;

    /// <summary>
    /// Initializes a new instance of the <see cref="ImmutableException"/> class.
    /// </summary>
    /// <param name="message">
    /// The message.
    /// </param>
    public ImmutableException(string message)
        : base(message)
    {
    }


    public ImmutableException(string message, string locationName)
        : base(message)
    {
        this.locationName = locationName;
    }


    public string LocationName
    {
        get
        {
            return this.locationName;
        }
    }
}

次に、このようにクラスに適用します

    [PseudoImmutableAttribute]
public class TestClass
{



    public string MyString { get; set; }




    public int MyInitval { get; set; }
}

次に、マルチスレッドで実行します

 /// <summary>
/// The program.
/// </summary>
public class Program
{
    /// <summary>
    /// The main.
    /// </summary>
    /// <param name="args">
    /// The args.
    /// </param>
    public static void Main(string[] args)
    {
        Console.Title = "Divyang Demo ";
        var w = new Worker();
        w.Run();
        Console.ReadLine();
    }
}


internal class Worker
{

    private object SyncObject = new object();


    public Worker()
    {
        var r = new Random();
        this.ObjectOfMyTestClass = new MyTestClass { MyInitval = r.Next(500) };
    }


    public MyTestClass ObjectOfMyTestClass { get; set; }


    public void Run()
    {

        Task readWork;

        readWork = Task.Factory.StartNew(

            action: () =>
                {
                    for (;;)
                    {
                        Task.Delay(1000);
                        try
                        {
                            this.DoReadWork();
                        }
                        catch (Exception exception)
                        {
                            // Console.SetCursorPosition(80,80);
                            // Console.SetBufferSize(100,100);
                            Console.WriteLine("Read Exception : {0}", exception.Message);
                        }
                    }
                    // ReSharper disable FunctionNeverReturns
                });


        Task writeWork;


        writeWork = Task.Factory.StartNew(

            action: () =>
                {
                    for (int i = 0; i < int.MaxValue; i++)
                    {
                        Task.Delay(1000);
                        try
                        {
                            this.DoWriteWork();
                        }
                        catch (Exception exception)
                        {
                            Console.SetCursorPosition(80, 80);
                            Console.SetBufferSize(100, 100);
                            Console.WriteLine("write Exception : {0}", exception.Message);
                        }

                        if (i == 5000)
                        {

                            ((IPseudoImmutable)this.ObjectOfMyTestClass).Freeze();

                        }
                    }
                });

        Task.WaitAll();
    }

    /// <summary>
    /// The do read work.
    /// </summary>
    public void DoReadWork()
    {
        // ThreadId  where reading is done
        var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

        // printing on screen
        lock (this.SyncObject)
        {
            Console.SetCursorPosition(0, 0);
            Console.SetBufferSize(290, 290);
            Console.WriteLine("\n");
            Console.WriteLine("Read Start");
            Console.WriteLine("Read => Thread Id: {0} ", threadId);
            Console.WriteLine("Read => this.objectOfMyTestClass.MyInitval: {0} ", this.ObjectOfMyTestClass.MyInitval);
            Console.WriteLine("Read => this.objectOfMyTestClass.MyString: {0} ", this.ObjectOfMyTestClass.MyString);


            Console.WriteLine("Read End");
            Console.WriteLine("\n");
        }
    }

    /// <summary>
    /// The do write work.
    /// </summary>
    public void DoWriteWork()
    {
        // ThreadId  where reading is done
        var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

        // random number generator
        var r = new Random();
        var count = r.Next(15);

        // new value for Int property
        var tempInt = r.Next(5000);
        this.ObjectOfMyTestClass.MyInitval = tempInt;

        // new value for string Property
        var tempString = "Randome" + r.Next(500).ToString(CultureInfo.InvariantCulture);

        this.ObjectOfMyTestClass.MyString = tempString;


        // printing on screen
        lock (this.SyncObject)
        {
            Console.SetBufferSize(290, 290);
            Console.SetCursorPosition(125, 25);

            Console.WriteLine("\n");
            Console.WriteLine("Write Start");
            Console.WriteLine("Write => Thread Id: {0} ", threadId);
            Console.WriteLine("Write => this.objectOfMyTestClass.MyInitval: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyInitval, tempInt);
            Console.WriteLine("Write => this.objectOfMyTestClass.MyString: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyString, tempString);
                          Console.WriteLine("Write End");
            Console.WriteLine("\n");
        }
    }
}





but still it will allow you to change property like array ,list . but if you apply more login in that then it may work for all type of property and field
于 2013-12-20T16:11:35.537 に答える