6

スタックにアイテムが利用可能になるまでブロックするスタックの周りのデータ構造を設計しようとしています。私は使ってみましたAutoResetEventが、その同期プロセスがどのように機能するかを誤解したと思います。基本的に、次のコードを見て、利用可能なものがないときにスタックからポップしようとしています。

AutoResetEventはセマフォのように動作しているようです。あれは正しいですか?私はただそれを取り除き、それSet()で終わらせることができBlockingStack.Get()ますか?または、スタックアイテムの1つだけを使用している状況になりますか。

public class BlockingStack
{
    private Stack<MyType> _internalStack;
    private AutoResetEvent _blockUntilAvailable;

    public BlockingStack()
    {
        _internalStack = new Stack<MyType>(5);
        _blockUntilAvailable = new AutoResetEvent(false);

        for (int i = 0; i < 5; ++i)
        {
            var obj = new MyType();
            Add(obj);
        }
    }

    public MyType Get()
    {
        _blockUntilAvailable.WatiOne();

        lock (_internalStack)
        {
            var obj = _internalStack.Pop();
            if (_internalStack.Count > 0)
            {
                _blockUntilAvailable.Set(); // do I need to do this?
            }

            return obj;
        }
    }

    public void Add(MyType obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);
            _blockUntilAvailable.Set();
        }
    }
}

私の仮定は、関数呼び出しAutoResetEventを通過したときに待機中のすべてのスレッドがリセットされることでした。WaitOne()ただし、複数のスレッドが入っているようです。どこかでロジックを台無しにしない限り。

編集:これはSilverlight用です。

4

3 に答える 3

6

スレッド化がどのように機能するかを理解しようとしているだけでない限り、ブロッキングコレクションを使用する方がよいでしょう。これにより、スタックに裏打ちされたブロッキングコレクションが得られます。

ConcurrentStack<SomeType> MyStack = new ConcurrentStack<SomeType>();
BlockingCollection<SomeType> SharedStack = new BlockingCollection<SomeType>(MyStack)

その後、すべてのブロックが適切に行われた状態で、スレッドセーフな方法でアクセスできます。こちらをご覧ください

sharedStackを呼び出すsharedStack.Take()と、スタックから何かを取得するまで取得をブロックします。


編集:私はしばらく(そして2回の試行)かかりましたが、私はあなたの問題を解決したと思います。

イベントを待機している3つのスレッドを持つ空のスタックについて考えてみます。

Addが呼び出され、スタックには1つのオブジェクトがあり、1つのスレッドがイベントを通過できます。

すぐにAddが再度呼び出されます。

これで、最初のスレッドはAddからロックを取得するのを待機します。

Addは、2番目のオブジェクトをスタックに追加し、別のスレッドがイベントを通過できるようにします。

これで、スタック上の2つのオブジェクトと、イベントを通過する2つのスレッドが、両方ともロックを待機しています。

First Getスレッドは、ロックされてポップするようになりました。スタック上の1つのオブジェクトが静止していることを確認し、CALLSSETを実行します。

イベントを通じて許可された3番目のスレッド。

2番目のGetスレッドがロックされてポップするようになりました。スタックには何も表示されず、setを呼び出しません。

しかし。手遅れです。3番目のスレッドはすでに通過を許可されているため、2番目のスレッドがロックを放棄すると、3番目のスレッドは空のスタックからポップしようとしてスローします。

于 2011-12-16T19:49:38.290 に答える
1

いいえ、現在のコードは意味がありません。現時点では、Getメソッドが呼び出される(.WaitOne呼び出し)たびにスレッドをブロックしています。

あなたはおそらく次のようなものが欲しいでしょう:

public class BlockingStack<T>
{
    private Stack<T> _internalStack;
    private AutoResetEvent _blockUntilAvailable;

    public BlockingStack()
    {
        _internalStack = new Stack<T>(5);
        _blockUntilAvailable = new AutoResetEvent(false);
    }

    public T Pop()
    {
        lock (_internalStack)
        {
            if (_internalStack.Count == 0)
                _blockUntilAvailable.WaitOne();

            return _internalStack.Pop();
        }
    }

    public void Push(T obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);

            if(_internalStack.Count == 0)
                _blockUntilAvailable.Set();
        }
    }
}

アイデアは、の現在のアイテム数が0の場合、メソッド_internalStackからのシグナルを待機する必要があるということです。Pushシグナルが送信されると、次に進み、スタックからアイテムをポップします。


編集:上記のコードには2つの問題があります:

  1. でブロックPopすると.WaitOne、のロックが解除され ない_internalStackためPush、ロックを取得できなくなります。

  2. Pop同じスレッドで複数回呼び出されると、AutoResetEventの同じinitialStateを共有します-例:プッシュは AutoResetEvent、アイテムが追加されたときに通知します。これで、アイテムをポップすると、最初は正常に機能します。これは、実際にはにアイテムがあるため Stackです。ただし、2回目は値がないため、-Stackを呼び出して待機しますが、の呼び出しがこのイベントを通知したため、trueが返され、期待どおりに待機しません。.WaitOneAutoResetEventPush

(実用的な)代替案:

public class BlockingStack<T>
{
    private Stack<T> _internalStack;

    public BlockingStack()
    {
        _internalStack = new Stack<T>(5);
    }

    public T Pop()
    {
        lock (_internalStack)
        {
            if (_internalStack.Count == 0)
                Monitor.Wait(_internalStack);

            return _internalStack.Pop();
        }
    }

    public void Push(T obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);
            Monitor.Pulse(_internalStack);
        }
    }
}
于 2011-12-18T12:18:00.130 に答える
1

ベースのソリューションを検証しませんでしたMonitorが、機能しているように見えるセマフォベースのソリューションを作成しました。

public class Semaphore
{
    private int _count;
    private int _maximum;
    private object _countGuard;

    public Semaphore(int maximum)
    {
        _count = 0;
        _maximum = maximum;
        _countGuard = new object();
    }

    public void WaitOne()
    {
        while (true)
        {
            lock (_countGuard)
            {
                if (_count < _maximum)
                {
                    _count++;
                    return;
                }
            }
            Thread.Sleep(50);
        }
    }

    public void ReleaseOne()
    {
        lock (_countGuard)
        {
            if (_count > 0)
            {
                _count--;
            }
        }
    }
}

public class BlockingStack
{
    private Stack<MyType> _internalStack;
    private Semaphore _blockUntilAvailable;

    public BlockingStack()
    {
        _internalStack = new Stack<MyType>(5);
        _blockUntilAvailable = new Semaphore(5);

        for (int i = 0; i < 5; ++i)
        {
            var obj = new MyType();
            Add(obj);
        }
    }

    public MyType Get()
    {
        _blockUntilAvailable.WaitOne();

        lock (_internalStack)
        {
            var obj = _internalStack.Pop();
            return obj;
        }
    }

    public void Add(MyType obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);
            _blockUntilAvailable.ReleaseOne();
        }
    }
}
于 2011-12-30T17:45:03.003 に答える