11

これに対する答えを知っておくべきだと思いますが、壊滅的な間違いを犯している可能性がある場合に備えて、とにかく尋ねます.

次のコードは、エラー/例外なしで期待どおりに実行されます。

static void Main(string[] args)
{
    ManualResetEvent flag = new ManualResetEvent(false);
    ThreadPool.QueueUserWorkItem(s =>
    {
        flag.WaitOne();
        Console.WriteLine("Work Item 1 Executed");
    });
    ThreadPool.QueueUserWorkItem(s =>
    {
        flag.WaitOne();
        Console.WriteLine("Work Item 2 Executed");
    });
    Thread.Sleep(1000);
    flag.Set();
    flag.Close();
    Console.WriteLine("Finished");
}

もちろん、マルチスレッド コードではよくあることですが、テストが成功しても、これが実際にスレッド セーフであるとは証明されません。ドキュメントには、 a の後に何かをしようとすると未定義の動作が発生すると明確に記載されていますが、Close前に を置いた場合もテストは成功します。SetClose

私の質問は、メソッドを呼び出すときに、呼び出し元に制御を返す前に、待機中のすべてのManualResetEvent.Setスレッドに通知することが保証されているかということです。言い換えると、 への呼び出しがこれ以上ないことを保証できると仮定すると、ここでハンドルを閉じても安全ですか? または、状況によっては、このコードによって一部のウェイターがシグナルを受け取るのを妨げたり、?_WaitOneObjectDisposedException

ドキュメントSetには、「シグナル状態」になると書かれているだけです。ウェイターが実際にいつそのシグナルを受け取るかについては何も主張していないようです。

4

4 に答える 4

6

でシグナルを送信すると、ManualResetEvent.Setそのイベントを待機している (つまり でブロック状態にあるflag.WaitOne) すべてのスレッドが、呼び出し元に制御を返す前にシグナルが送信されることが保証されます。

もちろん、フラグを設定する可能性があり、フラグをチェックする前に何らかの作業を行っているため (または、複数のスレッドを作成している場合は nobugs が提案したように)、スレッドがそれを認識しない場合があります。

ThreadPool.QueueUserWorkItem(s =>
{
    QueryDB();
    flag.WaitOne();
    Console.WriteLine("Work Item 1 Executed");
});

フラグには競合があり、フラグを閉じると未定義の動作が発生する可能性があります。フラグはスレッド間の共有リソースです。すべてのスレッドが完了時に通知するカウントダウン ラッチを作成する必要があります。これにより、 での競合が解消されますflag

public class CountdownLatch
{
    private int m_remain;
    private EventWaitHandle m_event;

    public CountdownLatch(int count)
    {
        Reset(count);
    }

    public void Reset(int count)
    {
        if (count < 0)
            throw new ArgumentOutOfRangeException();
        m_remain = count;
        m_event = new ManualResetEvent(false);
        if (m_remain == 0)
        {
            m_event.Set();
        }
    }

    public void Signal()
    {
        // The last thread to signal also sets the event.
        if (Interlocked.Decrement(ref m_remain) == 0)
            m_event.Set();
    }

    public void Wait()
    {
        m_event.WaitOne();
    }
}
  1. 各スレッドは、カウントダウン ラッチで信号を送ります。
  2. メイン スレッドはカウントダウン ラッチを待機します。
  3. カウントダウン ラッチ シグナルの後、メイン スレッドはクリーンアップします。

結局のところ、最後に寝る時間は、問題を処理するための安全な方法ではありません。代わりに、マルチスレッド環境で 100% 安全になるようにプログラムを設計する必要があります。

更新: 単一のプロデューサー/複数のコンシューマー
ここでの前提は、プロデューサーが作成されるコンシューマーの数を知っていることCountdownLatchです。すべてのコンシューマーを作成した後、指定された数のコンシューマーで をリセットします。

// In the Producer
ManualResetEvent flag = new ManualResetEvent(false);
CountdownLatch countdown = new CountdownLatch(0);
int numConsumers = 0;
while(hasMoreWork)
{
    Consumer consumer = new Consumer(coutndown, flag);
    // Create a new thread with each consumer
    numConsumers++;
}
countdown.Reset(numConsumers);
flag.Set();
countdown.Wait();// your producer waits for all of the consumers to finish
flag.Close();// cleanup
于 2010-02-25T19:53:04.220 に答える
5

大丈夫ではありません。ここでは、2つのスレッドしか開始していないため、幸運に恵まれています。デュアルコアマシンでSetを呼び出すと、すぐに実行が開始されます。代わりにこれを試して、爆弾を見てください:

    static void Main(string[] args) {
        ManualResetEvent flag = new ManualResetEvent(false);
        for (int ix = 0; ix < 10; ++ix) {
            ThreadPool.QueueUserWorkItem(s => {
                flag.WaitOne();
                Console.WriteLine("Work Item Executed");
            });
        }
        Thread.Sleep(1000);
        flag.Set();
        flag.Close();
        Console.WriteLine("Finished");
        Console.ReadLine();
    }

元のコードは、他のタスクで非常にビジー状態の場合、古いマシンまたは現在のマシンでも同様に失敗します。

于 2010-02-25T19:38:33.510 に答える
0

私の考えでは、競合状態があります。条件変数に基づいてイベントオブジェクトを作成すると、次のようなコードが得られます。

mutex.lock();
while (!signalled)
    condition_variable.wait(mutex);
mutex.unlock();

そのため、イベントが通知される場合でも、イベントを待機しているコードは、イベントの一部にアクセスする必要がある場合があります。

Closeのドキュメントによると、これは管理されていないリソースのみを解放します。したがって、イベントで管理対象リソースのみを使用する場合は、幸運になる可能性があります。しかし、それは将来変更される可能性があるので、私は予防策の側で誤りを犯し、それがもはや使用されていないことがわかるまでイベントを閉じません。

于 2010-02-25T19:40:25.793 に答える
0

私には危険なパターンに見えますが、[現在の] 実装では問題ありません。まだ使用されている可能性のあるリソースを破棄しようとしています。

これは、オブジェクトを新しく作成して構築し、そのオブジェクトのコンシューマが完了する前であってもやみくもに削除するようなものです。

そうでなくても、ここで問題があります。他のスレッドが実行される前であっても、プログラムは終了する可能性があります。スレッド プール スレッドはバックグラウンド スレッドです。

とにかく他のスレッドを待たなければならないことを考えると、後でクリーンアップすることもできます。

于 2010-02-26T08:36:31.163 に答える