9

(オブジェクトインスタンスごとに)一度だけ呼び出すことができるスレッドセーフなメソッドを書き込もうとしています。以前に呼び出されたことがある場合は、例外をスローする必要があります。

私は2つの解決策を考え出しました。どちらも正しいですか?そうでない場合、それらの何が問題になっていますか?

  1. lock

    public void Foo()
    {
        lock (fooLock)
        {
            if (fooCalled) throw new InvalidOperationException();
            fooCalled = true;
        }
        …
    }
    private object fooLock = new object();
    private bool fooCalled;
    
  2. Interlocked.CompareExchange

    public void Foo()
    {
        if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1)
            throw new InvalidOperationException();
        …
    }
    private int fooCalled;
    

    私が間違っていなければ、このソリューションにはロックフリーであるという利点があり(私の場合は無関係のようです)、必要なプライベートフィールドが少なくて済みます。

また、どの解決策を優先すべきかについての正当な意見や、より良い方法がある場合はさらに提案することもできます。

4

3 に答える 3

7

あなたのInterlocked.CompareExchangeソリューションは最高に見え、(あなたが言ったように)ロックフリーです。また、他のソリューションよりも大幅に複雑ではありません。ロックは非常に重いですがCompareExchange、単一のCAScpu命令にコンパイルすることができます。私はそれと一緒に行くと言います。

于 2012-03-01T10:39:30.920 に答える
0

ダブルチェックされたロックパターンはあなたが求めているものです:

これはあなたが求めているものです:

class Foo
{
   private object someLock = new object();
   private object someFlag = false;


  void SomeMethod()
  {
    // to prevent locking on subsequent calls         
    if(someFlag)
        throw new Exception();

    // to make sure only one thread can change the contents of someFlag            
    lock(someLock)
    {
      if(someFlag)
        throw new Exception();

      someFlag = true;                      
    }

    //execute your code
  }
}

一般に、これらのような問題にさらされた場合は、上記のようなよく知られたパターンに従ってください。
これにより、特にスレッディングに関して、パターンに従うときに何かを見逃す可能性が低くなるため、認識可能になり、エラーが発生しにくくなります。
あなたの場合、最初のifはあまり意味がありませんが、多くの場合、実際のロジックを実行してからフラグを設定する必要があります。(おそらくかなりコストのかかる)コードを実行している間、2番目のスレッドはブロックされます。

2番目のサンプルについて:
はい、それは正しいですが、それよりも複雑にしないでください。単純なロックを使用しない非常に正当な理由があるはずです。この状況では、Interlocked.CompareExchange()何も達成せずにコードがより複雑になります(あまり知られていないため)(ブールフラグを設定するためのロックに対するロックが少ないことを指摘したように、実際にはこの場合のメリット)。

于 2012-03-01T10:29:45.837 に答える
-2
    Task task = new Task((Action)(() => { Console.WriteLine("Called!"); }));
    public void Foo()
    {
        task.Start();
    }

    public void Bar()
    {
        Foo();
        Foo();//this line will throws different exceptions depends on 
              //whether task in progress or task has already been completed
    }    
于 2012-03-01T10:30:35.400 に答える