1

Delphi 2006 を使用していますが、開発中のアプリケーションに少し問題があります。

私は、長い操作を実行する関数を呼び出すスレッドを作成するフォームを持っています。LengyProcess と呼びましょう。LengthyProcess 関数内では、独自のスレッドを作成するいくつかの Dll 関数も呼び出します。

私が抱えている問題は、スレッドの Synchronize 関数を使用して LengthyProcess を呼び出さないと、スレッドが応答を停止することです (メイン スレッドは引き続き正常に応答します)。メインスレッドがLengyProcessの終了を待っているため、別のスレッドを作成する目的が無効になるため、Synchronizeを使用したくありません。

スレッドを作成してから WaitFor を呼び出す dll 内の関数まで問題を追跡しました。これはすべて TThread を使用して行われます。WaitFor は、CurrentThreadID が MainThreadID と等しいかどうかを確認し、等しい場合は CheckSynchronization を呼び出します。すべて問題ありません。したがって、Synchronize を使用する場合、CurrentThreadID は MainThreadID と等しくなりますが、Synchronize を使用しない場合、もちろん CurrentThreadID <> MainThreadID となり、これが発生すると、WaitFor は現在のスレッド (私が作成したスレッド) に、 DLL であるため、CheckSynchronization が呼び出されることはなく、スレッドは dll で作成されたスレッドを永遠に待機することになります。

これが理にかなっているといいのですが、申し訳ありませんが、これ以上の説明方法がわかりません。他の誰かがこの問題を抱えていて、それを解決する方法を知っていますか?

4

3 に答える 3

7

セカンダリ スレッドが「応答を停止」した場合は、メッセージ ポンプがあると考えられます。(それ以外の場合は、何に対して応答停止するかを説明する必要があります。) また、3 次スレッドの実行が終了したときにスレッドが検出できるようにしたいと考えているようです。(ここでの「プライマリ」スレッドは VCL スレッドであり、まったく関与していません。)

を使用してみWaitForましたが、ブロックされていることがわかってがっかりしました。しかし、それは常にそうするように設計されてきたものです。メイン スレッドでのその動作はおかしくなるところなので、もともとそのように使用されることを意図していなかったとしても、VCL スレッドから呼び出しても安全です。

メッセージを処理、スレッドの実行が完了するまで待機するには、Windows API から1 つ以上の待機関数を使用する必要があります。から始めMsgWaitForMultipleObjectsます。スレッド ハンドルを含むさまざまな種類のカーネル ハンドルを待機できますが、メッセージが利用可能になったときに通知することもできます。アイデアは、その関数をループで呼び出すことです。メッセージが利用可能であると表示されたら、それらを処理し、再度ループして待機を続けます。

以下はあくまでも概要です。使用されているすべての API 関数のドキュメントを確認し、それを自分のスレッドに関する残りの知識と組み合わせる必要があります。

procedure TSecondaryThread.Execute;
var
  ret: DWord;
  ThreadHandle: THandle;
  Msg: TMsg;
begin
  ThreadHandle := TertiaryThread.Handle;
  repeat
    ret := MsgWaitForMultipleObjects(1, ThreadHandle, False, Infinite, qs_AllEvents);
    case ret of
      Wait_Object_0: begin
        // The thread terminated. Do something about it.
        CloseHandle(ThreadHandle);
        PostQuitMessage(0);
        // Put *something* in the parameter so further calls to MWFMO
        // will have a valid handle. May as well use a handle to something
        // that will never become signaled so all we'll get are more
        // messages. I'm pretty sure you can't pass an empty array of
        // handles; there must be at least one, and it must be valid.
        ThreadHandle := Self.Handle;
      end;
      Wait_Object_0 + 1: begin
        // At least one message is available. Handle *all* of
        // them before calling MsgWaitForMultipleObjects again
        while PeekMessage(Msg, 0, 0, 0, pm_Remove) do
        case Msg.Message of
          wm_Quit: begin
            // Do something about terminating the tertiary thread.
            // Then stop the message loop and the waiting loop.
            Exit;
          end;
          else begin
            TranslateMessage(Msg);
            DispatchMessage(Msg);
          end;
        end;
      end;
      Wait_Timeout: Assert(False, 'Infinity has passed');
      Wait_Failed: RaiseLastOSError;
      else Assert(False, 'Unexpected return value');
    end;
  until False;
end;

すべてのメッセージの処理に関する部分が重要です。、、またはを呼び出すGetMessageとすぐに、OS はキュー内のすべてのメッセージを「古い」ものとしてマークしますが、キューに新しい」メッセージ (最後の.PeekMessageWaitMessageMsgWaitForMultipleObjectsPeekMessage

于 2010-01-22T05:54:01.217 に答える
0

Delphi DLL でTThreadクラスやオブジェクトを使用することは、非常に安全ではありません。RTL および VCL コア クラス、グローバル、およびシングルトン オブジェクトは、プロセスごとに 1 回存在するように設計されており、標準の DLL ライブラリでの名前空間の重複および不完全な初期化を処理できません。 EXE およびランタイム ユニットを参照するすべての DLL (特にフォームとクラス) で、ランタイム パッケージ (RTL と VCL で十分です。必要なシステム ユニットだけを使用して独自にビルドすることもできます) を使用してビルドすることで回避できます。 - この方法で、単一の共有名前空間と完全な EXE 初期化シーケンスを取得します。 DLL をまったく変更できない場合は、 、、およびApplication

Application.HandleMainThreadIDSyncEventWakeMainThreadメインEXEモジュールの対応する値に - これうまくいくかもしれませんが、見た目と同じくらい醜く、すべてのエッジケースをカバーしているわけではありません(クラスと重要なグローバルはまだ複製されます)。

于 2010-06-01T00:25:07.833 に答える
0

こんにちは、返信ありがとうございます。はい、私の質問はあまり明確ではなく、やや混乱していることに気づきました。だから私は物事を少し明確にしようとします、ここに行きます..

以下で説明するすべてのスレッドは、TThread から派生しています。

スレッドを開始するフォームがありますが、それを待ちません。フォームによって開始されたスレッドは、長いタスクを実行する関数を呼び出します。

関数が DLL 内の別の関数を呼び出し、DLL 内の関数がスレッドを開始して待機します。DLL 関数によって開始されたスレッドは、同期を介して別の関数を呼び出します。

フォーム -> スレッドを開始するが待機しない -> スレッドが関数を呼び出す -> 関数が DLL 関数を呼び出す -> DLL 関数がスレッドを開始して待機する -> DLL 関数によって開始されたスレッドが同期を介して別の関数を呼び出す同期 (UpdateRecords)。

問題は、同期の呼び出しが返されないことです。これは、私が見ることができるところから、ある種のデッドロックに入ったためです。

同期のしくみ: Synchronize はメソッド呼び出しをキューに入れ、イベントを設定します。その後、Synchronize はイベントがシグナル状態になるのを待ちます。メイン スレッドがアイドル状態の場合、キューで待機しているメソッド呼び出しを処理します。メソッド呼び出しを処理した後、関連付けられたイベントを通知して、同期を開始したスレッドが続行できるようにします。

フォームによって開始されたスレッドは、同期を使用して長いタスクを実行する関数を呼び出しません。同期を使用すると、アプリケーションはデッドロックしませんが、これは長いプロセスにスレッドを使用する目的を無効にします。

問題を追跡しました.dll によって作成された TApplication オブジェクトがメッセージを処理しておらず、ハンドルが 0 になっているようです.これがどのように起こったのかわかりません.他の人が書いたもの) ですが、synchronized で queued と呼ばれるメソッドを決して処理しないため、問題の原因となります。

同期を使用してスレッドから長いプロセスを実行する関数を呼び出すと、アプリケーションはデッドロックしないことを前に述べました。これは、メイン スレッドが長いプロセスを実行する関数の呼び出しを担当するためです。したがって、長いプロセス関数は、別のスレッドを開始してから WaitFor を呼び出す DLL 関数を呼び出します。WaitFor は、現在のスレッドがメイン スレッドであるかどうかを確認し、メイン スレッドである場合は、synchronize によってキューに入れられたメソッド呼び出しを、待機しているスレッドが解放されるまでループ内で継続的に処理します (つまり、そのスレッドが解放されるメソッド)。同期を介してキューに入れられたものが呼び出され、待機イベントが通知されます)。

WaitFor では、現在のスレッドがメイン スレッドでない場合、WaitFor は待機しているスレッドが解放されるまで単純にブロックします。

とにかく、dll は非常に複雑で、大規模なシステムで使用されるため、dll 内のアプリケーション オブジェクトについては何もできません。同期キュー内のメソッドを処理できる dll 内のメソッドを公開できると思います。その後、アイドル状態のアプリケーションからこのメソッドを呼び出すことができます。

とにかく、あなたの助けに感謝しますが、私は今この問題を解決しました。

于 2010-01-24T20:59:36.660 に答える