3

プライベート ローカル メッセージ キュー (MSMQ) を処理する Windows サービスがあります。開始時にイベント ハンドラーをPeekCompletedキューに登録し、非同期を呼び出してBeginPeek()メッセージの到着を待機します。

protected override void OnStart(string[] args)
{
    if (String.IsNullOrWhiteSpace(args[0]))
        return;

    queue = new MessageQueue(args[0]);
    queue.Formatter = new BinaryMessageFormatter();
    queue.PeekCompleted += new PeekCompletedEventHandler(OnPeekCompleted);

    queue.BeginPeek();
}

メッセージが到着したら、私の目標は明らかにそのメッセージを処理することです。私のコードには現在queue.Receive()、トランザクション内に含まれるメッセージを取得するメソッドがあり、処理中にエラーが発生した場合にメッセージがキューに戻されます。BeginPeek()サイクルを再開するために再度呼び出されます。

private static void OnPeekCompleted(Object source, PeekCompletedEventArgs asyncResult)
{
    try
    {
        MessageQueue q = (MessageQueue)source;

        using (MessageQueueTransaction trans = new MessageQueueTransaction())
        {
            trans.Begin();

            Message msg = q.Receive(trans);
            ProcessMessage(msg);

            trans.Commit();
         }

         // Restart the asynchronous peek operation.
         q.BeginPeek();
    }
    catch (MessageQueueException qEx)
    {
        // TODO: Walk it off.
    }
    return;
}

いつでもEndPeek()キューに電話する必要がありますか?

この質問が暗示しているように、おそらくメモリリークを避けるためですか?私はそうする必要はないと確信していますが、ドキュメントはそれについてあまり明確ではありません。何かを「終わらせる」ことなく「始める」のは100%正しいとは思えません:)

Receive()ところで:行をに置き換えることもできMessage msg = q.EndPeek(asyncResult.AsyncResult)ます。これにより、メッセージが同様にフェッチされますが、キューからメッセージが削除されません。

4

1 に答える 1

11

短い答え (「いいえ」) は誤解を招く可能性があるため、この質問に適切な答えを出すには多少の努力が必要です。

API の説明では、 を呼び出すたびに呼び出す必要があると明示的に述べていますか? 私が見つけることができるトピックではなく、それだけでなく、ここでは反対のことを述べているようです:EndPeek()BeginPeek()

を使用するBeginPeekには、非同期操作の結果を処理するイベント ハンドラーを作成し、それをイベント デリゲートに関連付けます。BeginPeek非同期ピーク操作を開始します。 メッセージがキューに到着すると、イベントの発生を通じて MessageQueue通知されます。PeekCompletedMessageQueue、 を呼び出すEndPeek(IAsyncResult) 、 を使用して結果を取得することにより、メッセージにアクセスできますPeekCompletedEventArgs

(私のものを強調してください。)これは、.EndPeek()を呼び出す義務を負わずに、イベント引数からメッセージを使用するか、直接取得できることを示しているよう.EndPeek()です。

.EndPeek()さて、物事を正しく機能させるためにあなたが呼び出す実装命令はありますか? 少なくともSystem.Messaging.NET 4.0 での実装については、答えはノーです。を呼び出す.BeginPeek()と、非同期操作が割り当てられ、完了のためにコールバックが登録されます。この操作に関連付けられているアンマネージ リソースは、このコールバックで部分的にクリーンアップされ、その後でのみイベント ハンドラーが呼び出されます。.EndPeek()は実際にはクリーンアップを行いません。操作がまだ完了していない場合は完了するのを待ち、エラーをチェックして、メッセージを返します。.EndPeek()したがって、イベント引数からメッセージを呼び出すか、メッセージにアクセスするか、まったく何もしないことができるのは事実です。すべて同じようにうまく機能しません。

残念ながら、そうです -- 「部分的にクリーンアップした」と言ったことに注意してください。の実装にMessageQueueは、非同期操作ごとに for を割り当てますManualResetEventが、それを破棄することはなく、これを完全にガベージ コレクターに任せるという問題があります。.NET 開発者はしばしばこれを行うことで激怒しますが、もちろん Microsoft 自身の開発者はそうではありません。どちらでも完璧。この質問OverlappedDataで説明されているリークがまだ関連しているかどうか、またはソースからすぐに明らかであるかどうかはテストしていませんが、驚くことではありません。

.Begin...()API には、その実装が望まれるものを残す可能性があるという他の警告サインがあります。最も顕著なのは、非同期操作の確立された/パターンに従わず.End...()、途中でイベント ハンドラーを導入し、他では見たことのない奇妙なハイブリッドを生成することです。Message次に、クラスを から継承させるという非常に疑わしい決定がComponentあります。これにより、すべてのインスタンスにかなりのオーバーヘッドが追加され、いつどのように破棄する必要があるかという問題が生じます...全体として、Microsoftの最高の仕事ではありません.

さて、これはすべて、電話をかける「義務」がないことを意味します.EndPeek()か? はい、それを呼び出しても呼び出さなくても、リソースのクリーンアップや正確性に関して機能的な違いはありません。しかし、そうは言っても、私のアドバイスはとにかくそれを呼び出すことです。なんで?他の .NET クラスで非同期操作パターンがどのように機能するかをよく知っている人は、呼び出しがそこにあることを期待し、そこに配置しないと、リソース リークにつながるバグのように見えるためです。アプリケーションに問題がある場合、そのような人は、そうでない「問題コード」を見て無駄な努力をするかもしれません。への呼び出しを考えると.EndPeek()残りの機械に比べてオーバーヘッドはごくわずかですが、プログラマーの節約はコストを補う以上のものであると思います。考えられる代替手段は、代わりにコメントを挿入して、呼び出していない理由を説明することです。.EndPeek()ただし、おそらく、これを理解するには、単に呼び出すよりも多くのプログラマサイクルが必要です。

.EndPeek()理論的には、それを呼び出すもう 1 つの理由は、必要な呼び出しを行うために API のセマンティクスが将来変更される可能性があることです。実際には、これが起こる可能性はほとんどありません。これは、Microsoft が伝統的にこのような破壊的変更を行うことに消極的であり (以前は合理的に呼び出されなかったコードが機能しなく.EndPeek()なるため)、既存の実装がすでに確立された慣行に違反しているためです。

于 2014-11-19T08:12:58.527 に答える