14

初めて正しく実行される次のスレッドコードがあります。その後、時々、スレッドのExecuteメソッドでAVを取得します。

デバッグ出力:モジュール'ListenOutputDebugString.exe'のアドレス00409C8CでのTProcesses.Executeアクセス違反。アドレス08070610の読み取りプロセスListenOutputDebugString.exe(740)

このAVを何が生成しているのかわかりません...

unit Unit3;

interface

uses
  Classes,
  StdCtrls,
  Windows,
  ExtCtrls,
  SysUtils,
  Variants,
  JvExGrids,
  JvStringGrid;

type
  TProcesses = class(TThread)
  private
    { Private declarations }
    FTimer :  TTimer;
    FGrid  :  TJvStringGrid;
    FJobFinished : Boolean;
    procedure OverrideOnTerminate(Sender: TObject);
    procedure DoShowData;
    procedure DoShowErrors;
    procedure OverrideOnTimer(Sender: TObject);
  protected
    procedure Execute; override;
  public
    constructor Create(aGrid : TJvStringGrid);overload;
  end;

implementation

{TProcesses }

var SharedMessage : String;
    ErrsMess      : String;
    lp            : Integer;

constructor TProcesses.Create(aGrid : TJvStringGrid);
begin
 FreeOnTerminate := True;
 FTimer := TTimer.Create(nil);
 FTimer.OnTimer := OverrideOnTerminate;
 FTimer.OnTimer := OverrideOnTimer;
 FTimer.Interval := 10000;
 FGrid := aGrid;
 inherited Create(false);
 FTimer.Enabled := true;
 FJobFinished := true;
end;

procedure TProcesses.DoShowData;
var wStrList : TStringList;
    wi,wj : Integer;
begin
// FMemo.Lines.Clear;
 for wi := 1 to FGrid.RowCount-1 do
  for wj := 0 to FGrid.ColCount-1 do
   FGrid.Cells[wj,wi] := '';
 try
  try
  wStrList := TStringList.Create;
  wStrList.Delimiter := ';';
  wStrList.StrictDelimiter := true;
  wStrList.DelimitedText := SharedMessage;
//  outputdebugstring(PChar('Processes list '+SharedMessage));
  FGrid.RowCount := wStrList.Count div 4;
  for wi := 0 to wStrList.Count-1 do
    FGrid.Cells[(wi mod 4), (wi div 4)+1] := wStrList[wi];
  Except on e:Exception do
   OutputDebugString(Pchar('TProcesses.DoShowData '+e.Message));
  end;
 finally
  FreeAndNil(wStrList);
 end;
end;

procedure TProcesses.DoShowErrors;
begin
// FMemo.Lines.Add('Error '+ ErrsMess);
 FGrid.Cells[1,1] := 'Error '+ ErrsMess;
 ErrsMess := '';
end;

procedure TProcesses.Execute;
  function EnumProcess(hHwnd: HWND; lParam : integer): boolean; stdcall;
  var
    pPid : DWORD;
    title, ClassName : string;
  begin
    //if the returned value in null the
    //callback has failed, so set to false and exit.
    if (hHwnd=NULL) then
    begin
      result := false;
    end
    else
    begin
      //additional functions to get more
      //information about a process.
      //get the Process Identification number.
      GetWindowThreadProcessId(hHwnd,pPid);
      //set a memory area to receive
      //the process class name
      SetLength(ClassName, 255);
      //get the class name and reset the
      //memory area to the size of the name
      SetLength(ClassName,
                GetClassName(hHwnd,
                             PChar(className),
                             Length(className)));
      SetLength(title, 255);
      //get the process title; usually displayed
      //on the top bar in visible process
      SetLength(title, GetWindowText(hHwnd, PChar(title), Length(title)));
      //Display the process information
      //by adding it to a list box
      SharedMessage := SharedMessage +
        (className +' ;'+//'Class Name = ' +
         title +' ;'+//'; Title = ' +
         IntToStr(hHwnd) +' ;'+ //'; HWND = ' +
         IntToStr(pPid))+' ;'//'; Pid = ' +
         ;//         +#13#10;
      Result := true;
    end;
  end;
begin
if FJobFinished  then
 begin
  try
   FJobFinished := false;
  //define the tag flag
   lp := 0; //globally declared integer
  //call the windows function with the address
  //of handling function and show an error message if it fails
  SharedMessage := '';
  if EnumWindows(@EnumProcess,lp) = false then
   begin
      ErrsMess := SysErrorMessage(GetLastError);
      Synchronize(DoShowErrors);
   end
   else
    Synchronize(DoShowData);
   FJobFinished := true;
  Except on e:Exception do
   OutputDebugString(Pchar('TProcesses.Execute '+e.Message));
  end;
 end
end;

procedure TProcesses.OverrideOnTerminate(Sender: TObject);
begin
 FTimer.Enabled := false;
 FreeAndNil(FTimer);
end;

procedure TProcesses.OverrideOnTimer(Sender: TObject);
begin
  Self.Execute;
end;

end.
4

6 に答える 6

37

スレッドでタイマーを使用することはありません。代わりに、システムイベントを作成し、関数を使用してスレッドの実行ループで指定された時間待機しWaitForSingleObjectます。この関数は、指定されたオブジェクト(この場合はイベント)がシグナル状態になるか、タイムアウト間隔が経過するまで待機します。

原則は簡単です。イベントをシグナルなしの状態で作成し、スレッドが終了するまでその状態を維持します。これにより、WaitForSingleObject関数呼び出しで指定された時間、スレッド実行ループをブロックするたびに関数がタイムアウトします。スレッドを終了することを決定したら、スレッドの終了フラグを設定し(できるだけ多くのことを確認する必要があります)、そのイベントをシグナル状態に設定します。これにより、WaitForSingleObject関数はすぐに戻ります。

スレッドタイマーをシミュレートする例を次に示します(2秒間隔=関数呼び出しの2番目のパラメーターとして使用される2000ms WaitForSingleObject)。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TTimerThread = class(TThread)
  private
    FTickEvent: THandle;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure FinishThreadExecution;
  end;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FTimerThread: TTimerThread;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
  FTimerThread := TTimerThread.Create(False);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FTimerThread.FinishThreadExecution;
end;

{ TTimerThread }

constructor TTimerThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FreeOnTerminate := True;
  FTickEvent := CreateEvent(nil, True, False, nil);
end;

destructor TTimerThread.Destroy;
begin
  CloseHandle(FTickEvent);
  inherited;
end;

procedure TTimerThread.FinishThreadExecution;
begin
  Terminate;
  SetEvent(FTickEvent);
end;

procedure TTimerThread.Execute;
begin
  while not Terminated do
  begin
    if WaitForSingleObject(FTickEvent, 2000) = WAIT_TIMEOUT then
    begin
      Synchronize(procedure
        begin
          Form1.Tag := Form1.Tag + 1;
          Form1.Caption := IntToStr(Form1.Tag);
        end
      );
    end;
  end;
end;

end.
于 2012-06-28T14:48:50.747 に答える
9

TTimerスレッドセーフではありません。限目。ワーカースレッドで使用しようとしないでください。

TTimer、ワーカースレッドのコンストラクターでインスタンス化されます。つまり、ワーカースレッド自体のコンテキストではなく、ワーカースレッドを作成しているスレッドのコンテキストでインスタンス化されます。これは、タイマーが同じスレッドコンテキストで実行され、OnTimerイベントアンドラーがワーカースレッドのコンテキストでトリガーされないことも意味します(存在する場合)。したがって、OnTimerハンドラーの本体はスレッドセーフである必要があります。

TTimer.OnTimerワーカースレッドのコンテキストでイベントをトリガーするには、代わりにスレッドのメソッド内をインスタンス化する必要がありTTimerますExecute()。しかし、それには別の落とし穴があります。 TTimerを使用して非表示のウィンドウを作成しますAllocateHWnd()。これはスレッドセーフではなく、メインスレッドのコンテキスト外で安全に使用することはできません。また、TTimer作成中のスレッドコンテキストには、スレッドにはないアクティブなメッセージループが必要です。

試行していることを実行するには、Win32 APISetTimer()関数を直接使用するように切り替えて(ウィンドウの必要性を回避できます)、スレッドにメッセージループを追加する必要があります(ウィンドウを使用するかどうかは引き続き必要です)。またはそうでない場合)、または別のタイミングメカニズムに切り替えます。CreateWaitableTimer()とを介して待機可能なタイマーを使用できます。WaitForSingleObject()この場合、ウィンドウやメッセージループは必要ありません。または、を介してマルチメディアタイマーを使用することもできますtimeSetEvent()(タイマーは独自のスレッドで実行されるため、マルチメディアタイマーのコールバックがスレッドセーフであることを確認してください)。

于 2012-06-28T21:36:11.557 に答える
2

まず、コンストラクターでTProcesses.Create(aGrid:TJvStringGrid); あなたが持っている:

FTimer.OnTimer := OverrideOnTerminate;
FTimer.OnTimer := OverrideOnTimer;

ここで、OverrideOnTerminateは起動しません。おそらく、スレッドOnTerminateをキャッチしたいと思うでしょう。

次に、Create(false)を継承した実行状態でスレッドを作成します。したがって、Executeは自動的に呼び出されます。実行が終了すると、DoTerminateが呼び出され、スレッドが破棄されます。

次に、タイマーがOnTimerを起動すると、複数回呼び出します。ここでは、スレッドがまだ存在していない可能性があります。タイマーが解放されず、デッドスレッドを開始しようとします。

いくつかのルールに従ってコードを書き直す必要があります。

  1. 実行は継続的に実行する必要があります。WaitForSingleObject / WaitForMultipleObjectsを使用して、スレッドを「スリープ」にすることができます。MSDNヘルプをご覧ください。
  2. これらの関数にはTimeoutパラメーターがあるため、TTimerはまったく必要ありません。

[編集]私はあなたのためにいくつかの有用なサンプルを見つけました(申し訳ありませんが、それは私によってテストされていません):

procedure TProcesses.Execute;
const  
 _SECOND = 10000000;  
var  
 lBusy : LongInt;  
 hTimer : LongInt;  
 liWaitTime : LARGE_INTEGER;  
begin  
  hTimer := CreateWaitableTimer(nil, True, 'WaitableTimer');
  liWaitTime.QuadPart := _SECOND * YOUR_NumberOfSeconds;
  SetWaitableTimer(hTimer, TLargeInteger(liWaitTime ), 0, nil, nil, False);  
  repeat  
    lBusy := MsgWaitForMultipleObjects(1, hTimer, False, INFINITE, QS_ALLINPUT);
    // CODE EXECUTED HERE EVERY YOUR_NumberOfSeconds
   Until lBusy = WAIT_OBJECT_0;  
   CloseHandle(hTimer);  
end;  

これを少し調整する必要があります。待機するオブジェクトをもう1つ追加します。CreateEvent関数で作成されたイベントです。スレッドを即座に終了する必要がある場合は、SetEvent関数を呼び出すだけです。

于 2012-06-28T13:13:34.877 に答える
1

タイマーが本当に新しいスレッド(TProcess)によって所有されているのか、メインスレッドによって所有されているのかを確認できますか?Windowsのタイマーは、プロセスではなくスレッドによって(リソースマネージャーの観点から)「所有」されます。タイマーがメインスレッドによって所有されている場合、OnTimerイベントはメインスレッドのコンテキストで実行されます。明示的にExecuteを呼び出した場合でも、ExecuteがたまたまTThreadの子孫である「オブジェクトの手順」。

とにかく、明示的にExecuteを呼び出すことはできません。このプロシージャは、スレッドの実行時に(新しいスレッドのコンテキストで)呼び出されます。

これを試してみてください。Execute内で、Windows API関数を使用してタイマーを作成し、alertableパラメーターをTRUEに設定して無限に待機します(SleepEx)。次に、タイマーは実際に新しいスレッドのコンテキストで起動します。または、OnTimerイベント(メインスレッドのコンテキストで)で、APCプロシージャ呼び出しをワーカースレッドに投稿することもできます(SleepExで待機し、アラート可能をTRUEに設定する必要があります)。まったく異なる代替手段:OnTimerイベントでスレッドオブジェクトを作成し、Execute内で通常の処理を実行します-終了後にオブジェクトが解放されるように、FreeOnTerminateをtrueに設定する必要があります。

最後に、そのEnumProcess関数(「オブジェクトのプロシージャ」???内で宣言された関数)をWinApi呼び出しに渡すことができるかどうかはわかりません。これがクラッシュの原因である可能性があります。グローバルレベルで宣言された関数が必要だと思います。

于 2012-06-28T13:46:35.937 に答える
1

@TLamaに感謝します、それは何年も後に私を助けます。私はコードをDelphi7に変換しました、多分それは誰かを助けます。新しいアプリケーションをコピーして貼り付け、Form1-> Inspector-> Events:OnCreateandOnDestroyをダブルクリックします。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TTimerThread = class(TThread)
  private
    FTickEvent: THandle;
    procedure ProcessGUI;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure FinishThreadExecution;
  end;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FTimerThread: TTimerThread;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Caption := 'Init...';//IntToStr(Form1.Tag);
  FTimerThread := TTimerThread.Create(False);
  Form1.Caption := IntToStr(Form1.Tag);
  Form1.Repaint;
  Application.ProcessMessages;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FTimerThread.FinishThreadExecution;
end;

{ TTimerThread }

constructor TTimerThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FreeOnTerminate := True;
  FTickEvent := CreateEvent(nil, True, False, nil);
end;

destructor TTimerThread.Destroy;
begin
  CloseHandle(FTickEvent);
  inherited;
end;

procedure TTimerThread.FinishThreadExecution;
begin
  Terminate;
  SetEvent(FTickEvent);
end;

procedure TTimerThread.Execute;
begin
  while not Terminated do
  begin
    if WaitForSingleObject(FTickEvent, 3000) = WAIT_TIMEOUT then
    begin
      Synchronize(ProcessGUI);
    end;
  end;
end;

procedure TTimerThread.ProcessGUI;
begin
  Form1.Tag := Form1.Tag + 3;
  Form1.Caption := IntToStr(Form1.Tag);
end;

end.
于 2018-03-12T17:29:00.527 に答える
0

スレッドはGUIコントロールで動作しています(TJvStringGridがGUIコントロールであると想定しています)。これは決して良い考えではなく、予期しない結果をもたらす可能性があります。他のスレッドは、メインスレッドがGUIのものに触れるべきではありません。

于 2012-06-28T13:17:40.630 に答える