0

1 つのコンシューマー タスクと 1 つのプロデューサー タスクを持つ WinForms アプリがあります。私のプロデューサー タスクは定期的に Web サービスに接続し、指定された数の文字列を取得します。これらの文字列は、ある種の同時固定サイズ FIFO キューに配置する必要があります。次に、コンシューマ タスクはこれらの文字列を処理し、SMS メッセージとして送信します (メッセージごとに 1 つの文字列)。プロデューサー タスクが呼び出す SOAP 関数には、取得する文字列の数を指定するパラメーターが必要です。この数は、キューで利用可能なスペースによって決まります。したがって、キューの最大サイズが 100 文字列で、次にプロデューサーが Web サービスをポーリングするときに 60 文字列がキューにある場合、その時点でキューに収まるのはそれだけなので、40 文字列を要求する必要があります。 .

固定サイズの FIFO キューを表すために使用しているコードは次のとおりです。

public class FixedSizeQueue<T>
{
    private readonly List<T> queue = new List<T>();
    private readonly object syncObj = new object();

    public int Size { get; private set; }

    public FixedSizeQueue(int size)
    {
        Size = size;
    }

    public void Enqueue(T obj)
    {
        lock (syncObj)
        {
            queue.Insert(0, obj);

            if (queue.Count > Size)
            {
                queue.RemoveRange(Size, queue.Count - Size);
            }
        }
    }

    public T[] Dequeue()
    {
        lock (syncObj)
        {
            var result = queue.ToArray();
            queue.Clear();
            return result;
        }
    }

    public T Peek()
    {
        lock (syncObj)
        {
            var result = queue[0];
            return result;
        }
    }

    public int GetCount()
    {
        lock (syncObj)
        {
            return queue.Count;
        }
    }

私のプロデューサー タスクは現在、Web サービスから必要な文字列の数を指定していませんが、キュー (q.GetCount()) 内の現在の項目数を取得し、それを最大キュー サイズ。ただし、GetCount() はロックを使用しますが、GetCount() が終了するとすぐに、コンシューマー タスクがキュー内の 10 個の文字列を処理できる可能性はありませんか?つまり、実際にはキューを 100% 維持することはできません。満杯?

また、私のコンシューマ タスクは、基本的に、SMS メッセージで送信する前に、キューの最初の文字列を「覗く」必要があります。メッセージを送信できない場合は、文字列をキュー内の元の位置に残す必要があります。これを達成するために私が最初に考えたのは、キューの最初の文字列を「のぞいて」SMS メッセージで送信し、送信が成功した場合はキューから削除することです。このようにして、送信が失敗した場合でも、文字列は元の位置でキューに残ります。それは合理的に聞こえますか?

4

1 に答える 1

1

これは幅広い質問なので、決定的な答えはありませんが、私の考えは次のとおりです。

ただし、GetCount() はロックを使用しますが、GetCount() が終了するとすぐに、コンシューマー タスクがキュー内の 10 個の文字列を処理できる可能性はありませんか?つまり、実際にはキューを 100% 維持することはできません。満杯?

はい、そうです。ただしsyncObj、Web サービスへのクエリの全期間にわたってロックオンしない限りです。しかし、プロデューサー/コンシューマーのポイントは、プロデューサーがより多くを取得している間に、コンシューマーがアイテムを処理できるようにすることです。これについてできることはあまりありません。ある時点で、キューが 100% いっぱいになることはありません。常に 100% 満杯だった場合、それは消費者が何もしていないことを意味します。

このようにして、送信が失敗した場合でも、文字列は元の位置でキューに残ります。それは合理的に聞こえますか?

おそらくですが、これをコーディングした方法では、Dequeue()操作はキューの状態全体を返し、それをクリアします。このインターフェイスが提供する唯一のオプションは、失敗した項目を後で処理するために再度キューに入れることです。これは完全に合理的な手法です。

また、処理するアイテムがあるまでコンシューマーが自身をブロックする方法を追加することも検討します。例えば:

public T[] WaitForItemAndDequeue(TimeSpan timeout)
{
    lock (syncObj) {
        if (queue.Count == 0 && !Monitor.Wait(syncObj, timeout)) {
            return null; // Timeout expired
        }

        return Dequeue();
    }
}

public T[] WaitForItem()
{
    lock (syncObj) {
        while (queue.Count != 0) {
            Monitor.Wait(syncObj);
        }

        return Dequeue();
    }
}

Enqueue()次に、リストを操作した後に呼び出すように変更する必要がMonitor.Pulse(syncObj)あります(メソッドの最後ですが、lockブロック内です)。

于 2012-09-28T20:11:49.573 に答える