PostMessageが時々メッセージを失う
-
22-07-2019 - |
質問
スレッド:
のマルチスレッドWindowsアプリケーションを作成しました
A –ユーザーの操作を処理し、Bからのデータを処理するWindowsフォームです。
B –時折データを生成し、2つのAを渡します。
スレッドセーフキューは、スレッドBからAにデータを渡すために使用されます。エンキューおよびデキュー機能は、Windowsクリティカルセクションオブジェクトを使用して保護されます。
enqueue関数が呼び出されたときにキューが空の場合、関数はPostMessageを使用して、キューにデータがあることをAに伝えます。この関数は、PostMessageへの呼び出しが正常に実行されることを確認し、失敗した場合は繰り返しPostMessageを呼び出します(PostMessageはまだ失敗していません)。
これは、ある特定のコンピューターが時折メッセージを失い始めるまで、かなり長い間うまくいきました。失うということは、PostMessageはBで正常に戻りますが、Aはメッセージを受信しません。これにより、ソフトウェアがフリーズしたように見えます。
すでにいくつかの受け入れ可能な回避策を考え出しました。 Windowsがこれらのメッセージを失っている理由と、これが1台のコンピューターでのみ発生している理由を知るのは興味深いです。
ここにコードの関連部分があります。
// Only called by B
procedure TSharedQueue.Enqueue(AItem: TSQItem);
var
B: boolean;
begin
EnterCriticalSection(FQueueLock);
if FCount > 0 then
begin
FLast.FNext := AItem;
FLast := AItem;
end
else
begin
FFirst := AItem;
FLast := AItem;
end;
if (FCount = 0) or (FCount mod 10 = 0) then // just in case a message is lost
repeat
B := PostMessage(FConsumer, SQ_HAS_DATA, 0, 0);
if not B then
Sleep(1000); // this line of code has never been reached
until B;
Inc(FCount);
LeaveCriticalSection(FQueueLock);
end;
// Only called by A
function TSharedQueue.Dequeue: TSQItem;
begin
EnterCriticalSection(FQueueLock);
if FCount > 0 then
begin
Result := FFirst;
FFirst := FFirst.FNext;
Result.FNext := nil;
Dec(FCount);
end
else
Result := nil;
LeaveCriticalSection(FQueueLock);
end;
// procedure called when SQ_HAS_DATA is received
procedure TfrmMonitor.SQHasData(var AMessage: TMessage);
var
Item: TSQItem;
begin
while FMessageQueue.Count > 0 do
begin
Item := FMessageQueue.Dequeue;
// use the Item somehow
end;
end;
解決
FCount
も FQueueLock
によって保護されていますか?そうでない場合、問題は、投稿されたメッセージが既に処理された後にインクリメントされる FCount
にあります。
これは何が起こっている可能性があるかです:
- Bはクリティカルセクションに入ります
- Bは
PostMessage
を呼び出します
- Aはメッセージを受信しますが、
FCount
は0
なので何もしません
- Bが
FCount
をインクリメントします
- Bはクリティカルセクションを離れます
- Aはあひるのように座っています
簡単な解決策は、 PostMessage
を呼び出す前に FCount
をインクリメントすることです。
特に予想されるよりも早く起こる可能性があることに注意してください(つまり、数行後にFCountをインクリメントする前に、PostMessageでポストされたメッセージが別のスレッドによってキャッチおよび処理されます)。真のマルチスレッド環境(複数のCPU)。だから私は以前に「問題のマシン」が複数のCPU /コアがありました。
このような問題をトラブルシューティングする簡単な方法は、メソッドを入力したり、クリティカルセクションに出入りするたびにログを記録する追加のログを使用してコードを足場にすることです。その後、ログを分析してイベントの真の順序を確認できます。
別の注意として、このような生産者/消費者シナリオで実行できる最適化は、1つではなく2つのキューを使用することです。コンシューマがフルキューを処理するためにウェイクアップすると、フルキューを空のキューとスワップし、フルキューをロック/処理するだけで、2つのスレッドが互いのキューをロックしようとせずに新しい空のキューを追加できます。ただし、2つのキューのスワップにはまだロックが必要です。
他のヒント
エンキュー時にキューが空の場合 関数が呼び出され、関数は PostMessageを使用して、Aにそのことを伝えます キュー内のデータです。
キューサイズを確認して PostMessage
を発行する前にメッセージキューをロックしていますか?実際にAが最後のメッセージを処理していて、アイドル状態になりそうなときに、キューをチェックして空ではないという競合状態が発生している可能性があります。
実際に競合状態を経験しているかどうかを確認するには、 PostMessage
の問題ではなく、イベントの使用に切り替えることができます。ワーカースレッド(A)は、メッセージを待つ代わりにイベントを待機します。 Bは、メッセージを投稿するのではなく、単にそのイベントを設定します。
これはかなり長い間うまくいきました 特定のコンピューターが起動するまで 時折メッセージを失います。
万が一、この特定のコンピューターのCPUまたはコアの数は、問題のない他のコンピューターとは異なりますか?シングルCPUマシンから複数の物理CPU /コアを持つマシンに切り替えると、新しい競合状態またはデッドロックが発生する場合があります。
知らないうちにメッセージを実行して食べ、処理済みとしてマークする2番目のインスタンスがありますか?