10

Dephi では、このようなスレッドを作成し、メイン フォームに時々メッセージを送信します。

Procedure TMyThread.SendLog(I: Integer);
Var
  Log: array[0..255] of Char;
Begin
  strcopy(@Log,PChar('Log: current stag is ' + IntToStr(I)));
   PostMessage(Form1.Handle,WM_UPDATEDATA,Integer(PChar(@Log)),0);
End;

procedure TMyThread.Execute;
var
  I: Integer;
begin
  for I := 0 to 1024 * 65536 do
  begin
    if (I mod 65536) == 0 then
    begin
      SendLog(I);
    End;
  End;
end;

ここで、WM_UPDATEDATA はカスタム メッセージで、以下に定義されています。

const
  WM_UPDATEDATA = WM_USER + 100;

メインフォームでは、次のようにしてリストを更新します。

procedure TForm1.WMUpdateData(var msg : TMessage);
begin
  List1.Items.Add(PChar(msg.WParam));
end;

ただし、メイン フォームに送信される Log 文字列はローカル変数であるため、SendLog を呼び出した後に破棄されます。TForm1.WMUpdateData はメッセージを非同期的に処理しますが、呼び出されたときにログ文字列がすでに破棄されている可能性があります。この問題を解決するには?

文字列スペースをグローバル システム スペースに割り当ててメッセージに渡すことができると思います。TForm1.WMUpdateData がメッセージを処理した後、グローバル スペースの文字列スペースを破棄できます。それは実行可能な解決策ですか?これを実装する方法は?

ありがとう

4

3 に答える 3

12

D2009 以降のバージョンを使用している場合、メイン フォームにメッセージを投稿する別の方法があります。TThread.Queueメソッドまたはプロシージャをメイン スレッドで実行できる、スレッドからの非同期呼び出しです。

ここでの利点は、メッセージ パッシングをセットアップするためのフレームがそれほど複雑ではないことです。スレッドを作成するときにコールバック メソッドを渡すだけです。ハンドルはなく、文字列の割り当て/割り当て解除の明示的な処理もありません。

Type
  TMyCallback = procedure(const s : String) of object;

  TMyThread = class(TThread)
    private
      FCallback : TMyCallback;
      procedure Execute; override;
      procedure SendLog(I: Integer);
    public
      constructor Create(aCallback : TMyCallback);
  end;

constructor TMyThread.Create(aCallback: TMyCallback);
begin
  inherited Create(false);
  FCallback := aCallback;
end;

procedure TMyThread.SendLog(I: Integer);
begin
  if not Assigned(FCallback) then
    Exit;
  Self.Queue(  // Executed later in the main thread
    procedure
    begin
      FCallback( 'Log: current stag is ' + IntToStr(I));
    end
  );
end;

procedure TMyThread.Execute;
var
  I: Integer;
begin
  for I := 0 to 1024 * 65536 do
  begin
    if ((I mod 65536) = 0) then
    begin
      SendLog(I);
    End;
  End;
end;

procedure TMyForm.TheCallback(const msg : String);
begin
  // Show msg
end;

procedure TMyForm.StartBackgroundTask(Sender : TObject);
begin
  ... 
  FMyThread := TMyThread.Create(TheCallback);
  ...
end;
于 2013-09-29T12:28:50.493 に答える
11

ローカル変数を投稿しているという事実に加えて、TWinControl.Handleプロパティもスレッドセーフではありません。TApplication.Handle代わりにプロパティを使用するか、 を使用AllocateHWnd()して独自のウィンドウを作成する必要があります。

ヒープに文字列を動的に割り当て、そのポインタをメイン スレッドにポストし、使い終わったらメモリを解放する必要があります。

例えば:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
  // or use a TApplicationEvents component...
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Application.OnMessage := nil;
end;

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
var
  S: PString;
begin
  if Msg.Message = WM_UPDATEDATA then
  begin
    S := PString(msg.LParam);
    try
      List1.Items.Add(S^);
    finally
      Dispose(S);
    end;
    Handled := True;
  end;
end;

procedure TMyThread.SendLog(I: Integer);
var
  Log: PString;
begin
  New(Log);
  Log^ := 'Log: current stag is ' + IntToStr(I);
  if not PostMessage(Application.Handle, WM_UPDATEDATA, 0, LPARAM(Log)) then
    Dispose(Log);
end;

または:

var
  hLogWnd: HWND = 0;

procedure TForm1.FormCreate(Sender: TObject);
begin
  hLogWnd := AllocateHWnd(LogWndProc);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if hLogWnd <> 0 then
    DeallocateHWnd(hLogWnd);
end;

procedure TForm1.LogWndProc(var Message: TMessage);
var
  S: PString;
begin
  if Message.Msg = WM_UPDATEDATA then
  begin
    S := PString(msg.LParam);
    try
      List1.Items.Add(S^);
    finally
      Dispose(S);
    end;
  end else
    Message.Result := DefWindowProc(hLogWnd, Message.Msg, Message.WParam, Message.LParam);
end;

procedure TMyThread.SendLog(I: Integer);
var
  Log: PString;
begin
  New(Log);
  Log^ := 'Log: current stag is ' + IntToStr(I);
  if not PostMessage(hLogWnd, WM_UPDATEDATA, 0, LPARAM(Log)) then
    Dispose(Log);
end;
于 2013-09-28T04:55:14.493 に答える