6

特定の種類のオブジェクトがあることを除いて、プロデューサー/コンシューマー キューがあります。したがって、すべてのコンシューマーが追加されたオブジェクトを消費できるわけではありません。種類が多すぎるため、種類ごとに特定のキューを作成したくありません。(生産者/消費者の定義を拡張するようなものですが、正しい用語が何であるかはわかりません。)

パラメータでパルスを許可する EventWaitHandle のようなものはありますか? 例えばmyHandle.Set(AddedType = "foo")。現在、私は使用してMonitor.Waitおり、各消費者はパルスが実際にそれらを意図したものであるかどうかを確認していますが、それはちょっと無意味に思えます.

私が今持っているものの疑似コードバージョン:

class MyWorker {
    public string MyType {get; set;}
    public static Dictionary<string, MyInfo> data;

    public static void DoWork(){
        while(true){
             if(Monitor.Wait(data, timeout)){
                   if (data.ContainsKey(MyType)){
                        // OK, do work
                   }
             }
        }
    }
}

ご覧のとおり、辞書に他のものが追加されると、パルスが発生することがあります。MyType が dict に追加されたときだけ気にします。それを行う方法はありますか?大したことではありませんが、たとえば、タイムアウト内でロックを取得するたびに成功する可能性があるため、手動でタイムアウトを処理する必要がありますが、内MyTypeの辞書に追加されることはありませんtimeout

4

2 に答える 2

3

これは興味深い質問です。ソリューションの鍵は、プライオリティ キューのブロッキング バリアントにあるようです。Java には がありますがPriorityBlockingQueue、残念ながら .NET BCL に相当するものは存在しません。ただし、一度取得すると、実装は簡単です。

class MyWorker 
{
    public string MyType {get; set;}
    public static PriorityBlockingQueue<string, MyInfo> data; 

    public static void DoWork()
    {
        while(true)
        {
            MyInfo value;
            if (data.TryTake(MyType, timeout, out value))
            {
                // OK, do work
            }
        }
    }
}

a の実装PriorityBlockingQueueはそれほど難しくありません。BlockingCollectionスタイルメソッドを利用するのAddと同じパターンに従ってTake、次のコードを思いつきました。

public class PriorityBlockingQueue<TKey, TValue>
{
    private SortedDictionary<TKey, TValue> m_Dictionary = new SortedDictionary<TKey,TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (m_Dictionary)
        {
            m_Dictionary.Add(key, value);
            Monitor.Pulse(m_Dictionary);
        }
    }

    public TValue Take(TKey key)
    {
        TValue value;
        TryTake(key, TimeSpan.FromTicks(long.MaxValue), out value);
        return value;
    }

    public bool TryTake(TKey key, TimeSpan timeout, out TValue value)
    {
        value = default(TValue);
        DateTime initial = DateTime.UtcNow;
        lock (m_Dictionary)
        {
            while (!m_Dictionary.TryGetValue(key, out value))
            {
                if (m_Dictionary.Count > 0) Monitor.Pulse(m_Dictionary); // Important!
                TimeSpan span = timeout - (DateTime.UtcNow - initial);
                if (!Monitor.Wait(m_Dictionary, span))
                {
                    return false;
                }
            }
            m_Dictionary.Remove(key);
            return true;
        }
    }
}

これは迅速な実装であり、いくつかの問題があります。まず、私はそれをまったくテストしていません。SortedDictionary第 2 に、基礎となるデータ構造として( 経由で) 赤黒ツリーを使用します。つまり、TryTakeメソッドの複雑さは O(log(n)) になります。通常、プライオリティ キューの削除の複雑さは O(1) です。プライオリティ キューに選択される一般的なデータ構造はheapですが、いくつかの理由から実際にはスキップ リストの方が優れていることがわかりました。これらはどちらも .NET BCL には存在しないためSortedDictionary、このシナリオではパフォーマンスが劣るにもかかわらず、代わりに を使用しました。

ここで、これは実際には無意味な動作を解決しないことを指摘しておきWait/Pulseます。PriorityBlockingQueueクラスにカプセル化されているだけです。ただし、少なくともこれにより、コードのコア部分が確実にクリーンアップされます。

コードがキーごとに複数のオブジェクトを処理しているようには見えませんでしたが、ディクショナリに追加するときにQueue<MyInfo>、単純な古いオブジェクトの代わりに aを使用することで簡単に追加できます。MyInfo

于 2010-12-16T20:17:11.387 に答える
1

プロデューサー/コンシューマー キューをオブザーバー パターンと組み合わせたいようです。一般的なコンシューマー スレッドまたは複数のスレッドがキューから読み取り、イベントを必要なコードに渡します。この場合、オブザーバーに実際にシグナルを送るのではなく、特定の作業項目に関心のあるユーザーをコンシューマー スレッドが識別したときに呼び出すだけです。

.Net のオブザーバー パターンは、通常、C# イベントを使用して実装されます。オブジェクトのイベント ハンドラーを呼び出すだけで、1 つ以上のオブザーバーが呼び出されます。ターゲット コードは、作業の到着時に通知するイベントに自分自身を追加することによって、監視対象のオブジェクトに自分自身を登録する必要があります。

于 2010-12-16T16:38:07.073 に答える