1

TThreadの子供がいます。すべて正常に動作しますが、作成したスレッドを一括中断または再開するにはどうすればよいですか?または、(Button2Clickで作成された)2番目のスレッドのみを一時停止するにはどうすればよいですか?これが私のコードの一部です:

TMyThread = class(TThread)
private
  source_file, destination_file: string;
  total_size, current_size, download_item_id: integer;
protected
  procedure ShowResult;
  procedure Execute; override;
public
end;

var
 MyThread: TMyThread;

begin

procedure TMyThread.Execute;
 begin
  //Some code for download file here, it doesn't matter
 end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  MyThread := TMyThread.Create(True);
  MyThread.source_file :='http://example.com/download1.zip';
  MyThread.destination_file := 'c:\download1.zip';
  MyThread.download_item_id := 0;
  MyThread.Priority := tpNormal;
  MyThread.FreeOnTerminate := True;
  MyThread.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread := TMyThread.Create(True);
  MyThread.source_file :='http://example.com/download2.zip';
  MyThread.destination_file := 'c:\download2.zip';
  MyThread.download_item_id := 1;
  MyThread.Priority := tpNormal;
  MyThread.FreeOnTerminate := True;
  MyThread.Resume;
end;

end.

つまり、このようなスレッドを作成すると、次のように機能します。

var
 MyThread1, MyThread2: TMyThread;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  MyThread1 := TMyThread.Create(True);
  MyThread1.source_file :='http://example.com/download1.zip';
  MyThread1.destination_file := 'c:\download1.zip';
  MyThread1.download_item_id := 0;
  MyThread1.Priority := tpNormal;
  MyThread1.FreeOnTerminate := True;
  MyThread1.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread2 := TMyThread.Create(True);
  MyThread2.source_file :='http://example.com/download2.zip';
  MyThread2.destination_file := 'c:\download2.zip';
  MyThread2.download_item_id := 1;
  MyThread2.Priority := tpNormal;
  MyThread2.FreeOnTerminate := True;
  MyThread2.Resume;
end;

//Terminate all of TMyThread
procedure TForm1.Button3Click(Sender: TObject);
begin
  MyThread1.Terminate;
  MyThread2.Terminate;
  ShowMessage('All downloads were terminated!');
end;

//Terminate ONLY the second of TMyThread
procedure TForm1.Button4Click(Sender: TObject);
begin
  MyThread2.Terminate;
  ShowMessage('The second download was terminated!');
end;

しかし、(最初のコードサンプルのように)動的に作成されたTMyThreadのセットに対してそれを行うにはどうすればよいですか?

4

2 に答える 2

8

制限

本当に、終了時に解放されるように設定されているスレッドへの参照を保持するべきではありません。それはあらゆる種類の問題を求めています。以下のコードは、FreeOnTerminateがTrueに設定されたスレッドを使用します。これは次の理由でのみ安全です。1)スレッドが終了するとき、およびスレッドが解放される前に、参照が削除される。2)OnTerminateハンドラーがメインスレッドのコンテキストで呼び出されるか、スレッドセーフなTListの子孫が使用されます。そして-最も重要な-3)参照は、メインスレッドのコンテキスト内から選択的にスレッドをキャンセルするためにのみ使用されます。

参照を他の何か(一時停止や再開など)に使用したい場合、またはメインスレッド以外のスレッドのコンテキスト内からそれらにアクセスできるようになったら、スレッド化されたプログラム実行のさまざまなメカニズムを深く掘り下げる必要があります。

いくつかのキーワード:スレッド同期、イベントシグナリング、ミューテックス、セマフォ、クリティカルセクション。

Delphiを使用したマルチスレッドプログラミングの良い参考資料は、マルチスレッド-DelphiWayです。

答え

後で本当にスレッドを参照する必要がある場合は、参照をどこかに保存する必要があります。ただし、作成するスレッドごとに個別の変数を設定する必要はありません。多くのオプションがあります。Contnrs配列が思い浮かびますが、おそらく最も単純なのは(ユニットからの)オブジェクトリストです。

TForm1 = class(TForm)
  private
    MyThreads: TObjectList;

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  MyThreads := TObjectList.Create;
end;

destructor TForm1.Destroy;
begin
  MyThreads.Free;
  inherited;
end;

TObjectListには、OwnsObjectsデフォルトで。であるプロパティがありますTrue。これは、オブジェクトリストを解放すると、それに含まれるインスタンスも解放されることを意味します。これは、リストに有効な参照のみが含まれていることを確認する必要があることを意味します。FreeOnTerminate通常、これは問題ではありませんが、スレッドでは、特にset toを使用する場合は、特別な注意が必要ですTrue

最初の例と同じようにスレッドを作成しますが、いくつか変更を加えます。

ローカル変数を使用します(グローバル変数は、ユニット内でのみ表示される場合でも、できるだけ避ける必要があります)。

オブジェクトリストにスレッドを追加します。

'FreeOnTerminate`をTrueに設定して使用する場合も、オブジェクトリストからインスタンスを削除するようにOnTerminateハンドラーを設定します。

procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TThread;                                       // Local var
begin
  Thread := TMyThread.Create(True);
  MyThreads.Add(Thread);                                 // Add to list
  Thread.source_file :='http://example.com/download1.zip';
  Thread.destination_file := 'c:\download1.zip';
  Thread.download_item_id := 0;
  Thread.Priority := tpNormal;
  Thread.OnTerminate := HandleThreadTerminate;           // Ensure you keep list valid
  Thread.FreeOnTerminate := True;                        // Advice against this!
  Thread.Start;
end;

procedure TForm1.HandleThreadTerminate(Sender: TObject);
var
  idx: Integer;
begin
  // Acquire Lock to protect list access from two threads
  idx := MyThreads.IndexOf(Sender);
  if idx > -1 then
    MyThreads.Delete(idx);
  // Release lock
end;

HandleThreadTerminateプロシージャはある種のロックを使用する必要があるため、IndexOfとDeleteの間のスレッド切り替えは間違ったインスタンスの削除を意味する可能性があるため、2つのスレッドが同時にリストからインスタンスを削除しようとすることはできません。ハンドラーのコードは、と書くことができますMyThreads.Remove(Sender)。これは単一のステートメントですが、舞台裏で示したコードと同じように実行されるため、スレッドセーフではありません。

編集:実際、@ LURDが言及しているように、OnTerminateはメインスレッドのコンテキストで呼び出されるため、スレッドセーフであることが起こります。TThreadListの例は、複数のスレッドからリストにアクセスする必要がある/たまたまアクセスする必要がある場合に行うのに適した方法であるため、ここでは残しておきます。

ロック機能を自動的に処理するには、 (ユニットからの) TThreadListを使用できますclasses。必要なコードの変更:

もちろん、TObjectListではなくTThreadListをインスタンス化する必要があります。

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  MyThreads := TThreadList.Create;
end;

TThreadListにはの概念がないためOwnsObjects、残りのスレッドを自分で解放する必要があります。

destructor TForm1.Destroy;
var
  LockedList: TList;
  idx: integer;
begin
  LockedList := MyThreads.LockList;
  try
    for idx := LockedList.Count - 1 downto 0 do
      TObject(MyThreads.Items(idx)).Free;
  finally
    MyThreads.UnlockList;
  end;

  MyThreads.Free;
  inherited;
end;

TThreadListが必要なロックを処理するため、OnTerminateハンドラーを安全に簡略化できるようになりました。

procedure TForm1.HandleThreadTerminate(Sender: TObject);
begin
  MyThreads.Remove(idx);
end;

編集:@mghieが述べているように、FreeOnTerminateが真のときにスレッドを解放することは、終了時に解放するという考え全体と衝突します。スレッドを解放する前にFreeOnTerminateをfalseに設定するか(スレッドを終了します)、FreeOnTerminate処理を維持する場合は、OnTerminateハンドラーを切断してから、Terminateを使用してスレッドに終了を指示する必要があります。

destructor TForm1.Destroy;
var
  LockedList: TList;
  idx: integer;
begin
  LockedList := MyThreads.LockList;
  try
    for idx := LockedList.Count - 1 downto 0 do
    begin
      TThread(MyThreads.Items(idx)).OnTerminate := nil;
      TThread(MyThreads.Items(idx)).Terminate;
    end;
  finally
    MyThreads.UnlockList;
  end;

  MyThreads.Free;
  inherited;
end;
于 2012-12-16T11:31:56.670 に答える
1

答えは本当に簡単です。FreeOnTerminateタイプのTThreadを作成する場合、そのTThreadインスタンスへの外部参照をまったく利用しません。そうすることはロシアンルーレットをプレイするようなものです...それは遅かれ早かれあなたの顔に爆発します。はい、それを行うにはいくつかの方法がありますが、そうすべきではありません。

あなたができることは、親と子の間に共有コミュニケーション媒体を導入することです。TThreadインスタンスへの直接参照を伴わない、子スレッドからのステータス更新を伝達するさまざまな方法があります。(最もよく使用される方法であるカスタムWindowsメッセージを確認してください。)

本当に明白な(私が思うに)、単純な例。私はこれをこのメッセージボックス内にコーディングしているので、コンパイルされることを期待しないでください...これは、親と子の間で安全に通信できるように構成する必要がある別のメカニズムが必要であることを示すためです。いくつかのTThreadインスタンスへの参照。(これは1つの子スレッドを想定しています...そして、はい、これを行うには他の多くの方法があります。)

unit MyPubComm.pas;

initialization
uses ...;

type
  TThreadComm = class
  private
    fThreadIsBusy:Boolean;  //more likely a State of some sort
    fThreadMessage:String;
  public
   property ThreadIsBusy:Boolean read fThreadIsBusy write fThreadIsBusy;
   property ThreadMessage:String read fThreadMessage write fThreadMessage;
  end;

  function IsChildBusy:Boolean;
  function ChildMessage:String;
  procedure SetThreadMessage(const MessageText:String);

var
  pubThreadStatus:TThreadComm;


implementation

function IsChildBusy:Boolean;
begin
  pubThreadStatus.Monitor.Enter;
  try
    Result := pubThreadStatus.ThreadIsBusy;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;

function ChildMessage:String;
begin
  pubThreadStatus.Monitor.Enter;
  try
    Result := pubThreadStatus.ThreadMessage;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;

procedure SetThreadMessage(const MessageText:String);
begin
  pubThreadStatus.Monitor.Enter;
  try
    pubThreadStatus.ThreadMessage := MessageText;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;


initialization
  pubThreadStatus := TThreadCom.Create;
finalization
  pubThreadStatus.Free();

--------
unit MainAppCode.pas
...
implementation
Uses MyPubComm;

procedure TMyForm1.WorkingOnSomething();
begin
   if IsChildBusy() then  //safely check child state
   begin 
      label1.caption := 'Child thread busy...';
   end
   else
   begin
      label1.caption := ChildMessage();  //safely retrieve shared data
   end;
end;
------
MyThread.pas
...
implementation
Uses MyPubComm;

function TMyThread.WorkerBee();
begin
   If TimeToCommunicateThreadMessage then
   begin 
     SetThreadMessage(MyMessage);  //safely update shared data
   end;
end;

これは、WorkingOnSomething()の明らかな問題も示すはずです。IsChildBusy()のチェックとChildMessage()の呼び出しの間に、実際のビジー状態とメッセージテキストが変わる可能性があるため、より堅牢な通信方法が必要になります。

于 2012-12-18T06:15:38.107 に答える