2012-02-17 18 views
2

これはIs it safe to signal and immediately close a ManualResetEvent?と密接に関連しており、その問題に対する1つの解決方法を提供する可能性があります。ManualResetEvent.WaitOne()でObjectDisposedExceptionを捕捉しても安全ですか?

私は同じ仕事をしたいと思っているスレッドがたくさんありますが、そのうちの1つだけが許可されなければならず、他の人は作業者が完了してその結果を使用するまで待つべきです。

基本的には一度だけ作業をしたいと思っています。

更新:が、私はこれは、.NET 4のレイジー< T>を使用して対処することができ、初期化の問題がないことを追加してみましょう。一度はをタスクごとに1回とし、これらのタスクは実行時に決定されます。これは以下の簡単な例から明らかではないかもしれません。

上記の質問に対するHans Passantの回答から簡単な例を少し変更すると、以下のことが安全になると思います。

がWAITONEの呼び出しです:

static void Main(string[] args) 
{ 
    ManualResetEvent flag = new ManualResetEvent(false); 
    object workResult = null; 
    for (int ix = 0; ix < 10; ++ix) 
    { 
     ThreadPool.QueueUserWorkItem(s => 
     { 
      try 
      { 
       flag.WaitOne(); 
       Console.WriteLine("Work Item Executed: {0}", workResult); 
      } 
      catch (ObjectDisposedException) 
      { 
       Console.WriteLine("Finished before WaitOne: {0}", workResult); 
      } 
     }); 
    } 
    Thread.Sleep(1000); 
    workResult = "asdf"; 
    flag.Set(); 
    flag.Close(); 
    Console.WriteLine("Finished"); 
} 

が、私は私の質問のコアがあると思います(それだけでは説明したユースケースとは少し違うのですが、スレッドの用語とその関係で、それは同じです) WaitOneへの呼び出しが成功したことに相当するObjectDisposedExceptionのために、メモリ障壁に関して中止されましたか?

他のスレッドが変数workResultに安全にアクセスできるようにする必要があります。

私の推測:安全である必要があります。それ以外の場合、WaitOneはManualResetEventオブジェクトが最初に閉じられていることを安全にどのように判断できますか?

+0

、私は最初の場所で '閉じる()'を呼び出す気になぜ年後、私は疑問に思う:私はC#ので

ジョーAlbahariのスレッディングは、次のセクションでは、適用されるこの特定のケースでは、非常に有用であることが証明しました[GCだけでクロージャを処理させるかもしれません](http://stackoverflow.com/a/2358158/709537)未来の未定義のポイントです。 –

答えて

3

私が見たものである。

  • あなたのコードは、次の競合状態を示すので、あなたがObjectDisposedExceptionを得ている:すべてのスレッドがフラグを起動するために管理している前
    • flag.closeを呼び出すことができます.waitOne。あなたがこれを処理する方法

コードの実行がflag.waitOne後にいかに重要であるかに依存します。ここで

は一つのアプローチです:

本当にスピンアップされているすべてのスレッドが実行すべき場合は、flag.closeを呼び出す前に、いくつかの余分な同期を持つことができます。 Thread.QueueUserWorkItemの代わりにTask.FactoryStartNewを使用してこれを達成できます。タスクが完了する時に待っていたことができ、はその後あなたはflag.closeは、このように競合状態と処理する必要がなくなり呼び出しますObjectDisposedException

あなたのコードは、その後になる:あなたができるよう

static void Main(string[] args) 
    { 
     ManualResetEvent flag = new ManualResetEvent(false); 
     object workResult = null; 
     Task[] myTasks = new Task[10]; 
     for (int ix = 0; ix < myTasks.Length; ++ix) 
     { 
      myTasks[ix] = Task.Factory.StartNew(() => 
      { 
       flag.WaitOne(); 
       Console.WriteLine("Work Item Executed: {0}", workResult); 
      }); 
     } 

     Thread.Sleep(1000); 
     workResult = "asdf"; 
     flag.Set(); 
     Task.WaitAll(); // Eliminates race condition 
     flag.Close(); 
     Console.WriteLine("Finished"); 
    } 

タスクは、見ている競合状態を排除する余分な同期を可能にします。

注意事項として、ManualResetEvent.waitOneはメモリバリアを実行するため、workresult変数は、メモリバリアや揮発性読み取りを必要とせずに更新された最新のものになります。

あなたはreaaaaally 追加の同期を避けるために、あなたのアプローチで行くことによってObjectDisposed例外を処理しなければならないのであれば、あなたの質問に答えるために、私は破棄されたオブジェクトがあなたのためにメモリバリアを行っていない、あなたがしなければならないという主張は、 catchブロックにThread.MemoryBarrierを呼び出して、最新の値が読み込まれていることを確認してください。

しかし例外は高価であり、あなたが通常のプログラム実行のためにそれらを避けることができれば、私はそうするのが賢明であると信じています。

幸運を祈る!

+0

+1この素晴らしい答えをお寄せいただきありがとうございます。これは私が与えた例を修正し、良いアドバイスを提供します。私の例の問題は、それが単純すぎることだと思います。残念ながら、私は3.5(指定しているはずです)上にあるように、タスクを使用することはできません。メモリ障壁の問題btwは、関連するすべてのスレッドによってConcurrentDictionaryの(同じエントリに)アクセスすることによって処理されます。いずれにせよ、あなたの答えは実際には、Monitor wait/pulseAllなどの.netによって提供される他の同時実行性構造を調べるようになりました。 –

0

いくつかのポイント:

  1. これは、.NET 4の場合、Lazyはこれを行うには良い方法です。

  2. メモリバリアの観点から見ても、そうでないかはほとんど関係がありません。例外は決して通常のコードパスの一部であってはなりません。これは予想されるユースケースではないので、動作は未定義であると仮定します。ここで

+0

1)この例は実際には単純化されています。これは、単一のworkResultの初期化の問題ではありません。だからレイジーのルールを外す。 2)通常のコードパスの一部として例外をキャッチするのはとても悪いですか? –

+0

概念的には、「WaitHandle」派生物を持って、スレッドを「永続的な緑色」の状態にしてリソースを使用しなくなるまでブロックすることは可能であるが、すぐにそして静かに進んでください。リソースを破棄し、待機から 'ObjectDisposeException'をキャッチする以外に、このようなことを行う方法はありますか? – supercat

関連する問題