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 パターンでラップすることです。この目標を達成するために、私は次のことを試しました:
app.cs で BlockingCollection を宣言します。
private readonly static BlockingCollection<Message> g_ParseOperations = new BlockingCollection<Message>();
イベント ハンドラーの本体を変更して操作を追加します。
private static void MessageReceived(Message message) { g_ParseOperations.Add(message); }
アプリ コンストラクターからコレクションをポンプする新しいスレッドを作成します。
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」が発生します。