0

並列ループを使用してメッセージ キューからメッセージにアクセスするクラスを構築しています。この問題を説明するために、単純化されたバージョンのコードを作成しました。

public class Worker
{
    private IMessageQueue mq;
    public Worker(IMessageQueue mq)
    {
        this.mq = mq;
    }

    public int Concurrency
    {
        get
        {
            return 5;
        }
    }

    public void DoWork()
    {
        int totalFoundMessage = 0;

        do
        {
            // reset for every loop
            totalFoundMessage = 0;

            Parallel.For<int>(
                0,
                this.Concurrency,
                () => 0,
                (i, loopState, localState) =>
                {
                    Message data = this.mq.GetFromMessageQueue("MessageQueueName");

                    if (data != null)
                    {
                        return localState + 1;
                    }
                    else
                    {
                        return localState + 0;
                    }
                },
                localState =>
                {
                    Interlocked.Add(ref totalFoundMessage, localState);
                });
        }
        while (totalFoundMessage >= this.Concurrency);
    }
}

アイデアは、並列ループを制御するためにワーカー クラスに同時実行値を設定することです。各ループの後、メッセージ キューから取得するメッセージの数が同時実行数と等しい場合、キューにはさらに多くのメッセージがある可能性があると想定し、メッセージ数が同時実行数よりも小さくなるまでキューからフェッチし続けます。TPL コードは、 TPL Data Parallelism Issueの投稿にも触発されています。

メッセージキューとメッセージオブジェクトへのインターフェースがあります。

public interface IMessageQueue
{
    Message GetFromMessageQueue(string queueName);
}

public class Message
{
}

したがって、単体テスト コードを作成し、Moq を使用してIMessageQueueインターフェイスをモックしました。

    [TestMethod()]
    public void DoWorkTest()
    {
        Mock<IMessageQueue> mqMock = new Mock<IMessageQueue>();

        Message data = new Message();

        Worker w = new Worker(mqMock.Object);

        int callCounter = 0;
        int messageNumber = 11;
        mqMock.Setup(x => x.GetFromMessageQueue("MessageQueueName")).Returns(() =>
        {
            callCounter++;
            if (callCounter < messageNumber)
            {
                return data;
            }
            else
            {
                // simulate MSMQ's behavior last call to empty queue returns null
                return (Message)null;
            }
        }
        );

        w.DoWork();

        int expectedCallTimes = w.Concurrency * (messageNumber / w.Concurrency);
        if (messageNumber % w.Concurrency > 0)
        {
            expectedCallTimes += w.Concurrency;
        }

        mqMock.Verify(x => x.GetFromMessageQueue("MessageQueueName"), Times.Exactly(expectedCallTimes));
    }

Moqのアイデアを使用して、呼び出された時間に基づいて関数の戻り値を設定し、呼び出し時間に基づく応答を設定しました。

単体テスト中に、テスト結果が不安定であることに気付きました。複数回実行すると、ほとんどの場合、テストに合格することがわかりますが、さまざまな理由でテストに失敗することもあります。

何が原因で状況が発生したのかわかりません。あなたからの情報を探しています。ありがとう

4

2 に答える 2

1

問題は、モックGetFromMessageQueue()がスレッドセーフではなく、同時に複数のスレッドから呼び出していることです。++本質的にスレッドセーフでない操作です。

代わりに、ロックまたはを使用する必要がありますInterlocked.Increment()

Parallel.ForEach()また、コードでは、開始と停止にオーバーヘッドがあるため、並列処理の恩恵を受けない可能性があります。より良い方法は、while(またはdo- while) の中に(または - ) を入れることParallel.ForEach()です。その逆ではありません。

于 2013-02-16T13:30:24.253 に答える
0

私のアプローチは、再構築することです。タイミングや同時実行性などをテストするときは、通常、呼び出し (この場合は PLINQ の使用) を、多数のデリゲートを受け入れる別のクラスに抽象化するのが賢明です。その後、新しいクラスに対して正しい呼び出しが行われていることをテストできます。次に、新しいクラスははるかに単純で (単一の PLINQ 呼び出しのみ)、ロジックが含まれていないため、テストしないままにしておくことができます。

超重要なもの (生命維持システム、飛行機など) に取り組んでいる場合を除き、テストする価値よりも多くの問題が発生するため、この場合はテストしないことをお勧めします。フレームワークが期待どおりに PLINQ クエリを実行することを信頼します。テストする意味があり、プロジェクトやクライアントに価値を提供するものだけをテストする必要があります。

于 2013-02-15T17:34:35.243 に答える