1

古いソフトウェア バージョンの古い遅いマシンで問題なく動作していた非常に古いコード (15 年以上) があります。if が競合状態に失敗するため、現在はうまく機能しません。これは一般的な質問です。他のコードのパターンを認識できるように、このコードの失敗を知って予測する必要があった理由を教えてください。

procedure TMainform.portset(iComNumber:word);
begin
windows.outputdebugstring(pchar('portset ' + inttostr(icomnumber)));

with mainform.comport do
try
    if open then open := False; // close port
    comnumber:=iComNumber;
    baud:=baudrate[baudbox.itemindex];
    parity:=pNone;
    databits:=8;
    stopbits:=1;
    open:=true;
    flushinbuffer;
    flushoutbuffer;
    if open then mainform.statusb.Panels[5].text:=st[1,langnum] {Port open}
      else mainform.statusb.Panels[5].text:=st[2,langnum]; {port set OK}
except
  on E: exception do begin
    windows.OutputDebugString('exception in portset');
    mainform.statusb.Panels[5].text:=st[3,langnum];
    beep;
    beep;
  end;
end;
windows.outputdebugstring('portset exit');
end;

flushinbuffer は EnterCriticalSection() で保護されていることに注意してください。私の知る限り、他に保護されているものはありません。私の知る限り、メッセージ処理セクションはありません。しかし

このコードがクリック イベントから呼び出されると、途中で取得されてから、ペイント イベントによって中断されます。

私が行った唯一のトレースは、outputdebugstring を使用したものです。2 番目の文字列が終了時に表示される前に、最初の文字列が開始時に繰り返されていることがわかります。それは本当ですか、それとも幻想ですか?

トレースは次のようになります。

4.2595    [4680] graph form click event
4.2602    [4680] portset 1 'from click event handler'
4.2606    [4680] graph form paint event
4.2608    [4680] portset 1 'from paint event handler'
4.2609    [4680] portset exit

4.3373    [4680] portset exit

これは競合状態です。クリック イベント ハンドラーコードが完了する前に、フォームのペイント イベント ハンドラーが呼び出されるため、エラーが発生します。シリアルコードはAsyncProです。ねじコードなし。はい、さらにコードがあります。いいえ、「ポートセット 1」の前に特に何もしませんが、そこに到達する前にフォームに書き込みます。

with graphform do begin
    if not waitlab.Visible then begin
       waitlab.visible:=true;
       waitprogress.position:=0;
       waitprogress.visible:=true;
       waitprogress.max:=214;
    end;
end;
mainform.Statusb.panels[5].text:=gcap[10,langnum]; 

躊躇しないでください: 何が間違っているのでしょうか? 何を調べればよいでしょうか?

4

4 に答える 4

4

これは予想される動作です。 を開いたり閉じたりするTApdComPortと、具体的には名前が付けられた関数を呼び出すことによって、メッセージ キューが処理されますSafeYield

function SafeYield : LongInt;
    {-Allow other processes a chance to run}
  var
    Msg : TMsg;
  begin
    SafeYield := 0;
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin
      if Msg.Message = wm_Quit then
        {Re-post quit message so main message loop will terminate}
        PostQuitMessage(Msg.WParam)
      else begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
      {Return message so caller can act on message if necessary}
      SafeYield := MAKELONG(Msg.Message, Msg.hwnd);
    end;
  end;

これTApdComPortは非同期コンポーネントです。com ポートはバックグラウンド スレッドで管理され、ポートを開いたり閉じたりするには、それらのスレッドを開始するか、停止するように通知する必要があります。物事が同期するのに時間がかかる場合に備えて、コンポーネント サービスがメッセージ キューを解放するのを待っている間 (たとえば):

if Assigned(ComThread) then
begin
     {Force the comm thread to wake...}
     FSerialEvent.SetEvent;
     {... and wait for it to die}
     ResetEvent(GeneralEvent);
     while (ComThread <> nil) do
     SafeYield;
end;

ただし、あなたのケースでこれが問題になる理由を説明するには、あなた自身のコードを十分に示していません。ペイント ハンドラーで操作されている COM ポートについての David の指摘は有効だと思います。

于 2013-08-07T13:42:20.273 に答える
2

標準のペイント イベントは単独では発生せず、メッセージの取得によってのみトリガーできます。したがって、説明した方法でコードが中断される可能性がある唯一の方法は、シリアルコンポーネント自体、またはそれに割り当てたイベントハンドラーのいずれかが、呼び出し元のスレッドのメッセージキューに新しいメッセージを送り込む何かを行っている場合です。

于 2013-08-07T05:58:28.183 に答える
1

イベント ハンドラーの最初でポートを閉じているため、イベントを 2 回トリガーする可能性がある場合(つまりApplication.ProcessMessages、コードから任意の場所を呼び出したりTMainform.portset()、ワーカー スレッドから直接呼び出したりすることによって)、新しいインスタンスはポートを閉じます。古いものはそれを介して通信しようとするため、エラーが発生します。AFAISには2つの解決策があります:

  • より高速ですが、最も耐え難いのは、関数全体を Mutex (または同期オブジェクトではないが同期オブジェクトとして使用できるイベント) で保護することですが、これは作成したコーディング エラーを隠すだけです。

  • よりプロの解決策は、競合状態が発生する場所を見つけてから、コードを修正することです。Application.ProcessMessages()とへのすべての参照を検索することでそれを行うことができTMainform.portset()、それらが並列に呼び出されないようにします。上記の関数のいずれにも参照が見つからない場合でも、コードの複数のインスタンスを実行することによって問題が発生する可能性があります (複数の COM ポートが作成されないため:))。

于 2013-08-07T07:27:45.957 に答える
0

Remy Lebeau が質問に答えた功績を認められました。なぜなら、私が求めたように、それは一般的な質問に対する一般的な回答だったからです。しかし、Uwe Raabe に対する彼のコメントがなければ不十分だったでしょう。

そして、Remy Lebeau が正しいことを決定的に示したのは、コードが失敗した特定のポイントを指摘する J からの例外的な回答でした。

また、「WM_PAINT に応答するコードがポートセットを呼び出す理由」を尋ねてくれた David Heffernan にも感謝します。はい、簡単な修正は、ペイント イベント ハンドラーから通信コードへのパスをブロックすることでしたが、より一般的なポイントを認識せずにそれを行いました。

このような問題が他にもあるかどうかを確認するために、通信コードを調べます。また、このような問題が他にもあるかどうかを確認するために、イベント ハンドラーを調べます。質問を検討しました。

于 2013-08-08T07:47:29.883 に答える