1

WPF アプリケーションで、メッセージを公開しているサード パーティ ライブラリがあります。

メッセージは次のとおりです。

public class DialectMessage
{
    public string PathAndQuery { get; private set; }

    public byte[] Body { get; private set; }

    public DialectMessage(string pathAndQuery, byte[] body)
    {
        this.PathAndQuery = pathAndQuery;
        this.Body = body;
    }
}

そして、app.cs ファイルから外部メッセージ ソースをセットアップします。

public partial class App : Application
{
    static App()
    {
        MyComponent.MessageReceived += MessageReceived;
        MyComponent.Start();
    }

    private static void MessageReceived(Message message)
    {
        //handle message
    }

}

これらのメッセージは一度に複数のスレッドから発行できるため、イベント ハンドラーを一度に複数回呼び出すことができます。

着信メッセージを解析する必要があるサービス オブジェクトがあります。このサービスは、次のインターフェースを実装します。

internal interface IDialectService
{
    void Parse(Message message);
}

そして、app.cs ファイルに既定の静的インスタンスがあります。

    private readonly static IDialectService g_DialectService = new DialectService();

パーサーのコードを単純化するために、一度に 1 つのメッセージのみが解析されるようにしたいと考えています。

また、サード パーティ オブジェクトをブロックしたくないため、イベント ハンドラーでのロックを回避したいと考えています。

g_DialectService.Parseこの要件のため、メッセージ イベント ハンドラーから直接呼び出すことはできません。

このシングルスレッドの実行を確実にする正しい方法は何ですか?

私の最初のことは、解析操作を Produce/Consumer パターンでラップすることです。この目標を達成するために、私は次のことを試しました:

  1. app.cs で BlockingCollection を宣言します。

    private readonly static BlockingCollection<Message> g_ParseOperations = new BlockingCollection<Message>();
    
  2. イベント ハンドラーの本体を変更して操作を追加します。

    private static void MessageReceived(Message message)
    {
        g_ParseOperations.Add(message);
    }
    
  3. アプリ コンストラクターからコレクションをポンプする新しいスレッドを作成します。

    static App()
    {
        MyComponent.MessageReceived += MessageReceived;
        MyComponent.Start();
    
        Task.Factory.StartNew(() =>
        {
            Message message;
            while (g_ParseOperations.TryTake(out message))
            {
                g_DialectService.Parse(message);
            }
        });
    }
    

ただし、このコードは機能しないようです。サービスの Parse メソッドが呼び出されることはありません。

さらに、このパターンでアプリケーションを適切にシャットダウンできるかどうかもわかりません。

すべてが機能していることを確認するには、コードで何を変更する必要がありますか?

PS:私は.Net 4.5をターゲットにしています

[編集] いくつかの検索とken2k の回答の後、 take の代わりに trytake を間違って呼び出していたことがわかります

私の更新されたコードは次のとおりです。

    private readonly static CancellationTokenSource g_ShutdownToken = new CancellationTokenSource();

    private static void MessageReceived(Message message)
    {
        g_ParseOperations.Add(message, g_ShutdownToken.Token);
    }

    static App()
    {
        MyComponent.MessageReceived += MessageReceived;
        MyComponent.Start();

        Task.Factory.StartNew(() =>
        {
            while (!g_ShutdownToken.IsCancellationRequested)
            {
                var message = g_ParseOperations.Take(g_ShutdownToken.Token);
                g_DialectService.Parse(message);
            }
        });
    }

    protected override void OnExit(ExitEventArgs e)
    {
        g_ShutdownToken.Cancel();
        base.OnExit(e);
    }

このコードは期待どおりに機能します。メッセージは正しい順序で処理されます。ただし、アプリケーションを終了するとすぐに、直前に IsCancellationRequested をテストしただけでも、Take メソッドで「CancelledException」が発生します。

4

1 に答える 1

2

ドキュメントには次のように書かれていBlockingCollection.TryTake(out T item)ます:

コレクションが空の場合、このメソッドはすぐに false を返します。

したがって、基本的にループはすぐに終了します。代わりに、タイムアウト パラメータを指定してTryTake メソッドを呼び出し、mustStop変数がtrue次のようになったときにループを終了することもできます。

bool mustStop = false;  // Must be set to true on somewhere else when you exit your program
...
while (!mustStop)
{
    Message yourMessage;

    // Waits 500ms if there's nothing in the collection. Avoid to consume 100% CPU
    // for nothing in the while loop when the collection is empty.
    if (yourCollection.TryTake(out yourMessage, 500))
    {
        // Parses yourMessage here
    }
}

編集した質問について: を受け取ったことを意味する場合は、OperationCanceledExceptionそれで問題ありません。CancellationTokenオブジェクトをパラメーターとして受け取るメソッドが動作する必要があるのとまったく同じです:)例外をキャッチして、正常に終了します。

于 2012-07-20T12:10:35.420 に答える