2017-03-07 16 views
0

どのように動作しているかを追うことができませんでした。TThread.CreateAnonymousThreadの奇妙な動作

私の状況をより良く説明しようとすると、まず簡単な例があります。 このコードは、新しいプロジェクトで作成された新しいForm Form1の内部にあります。 mmo1はメモコンポーネントです。

TOb = class 
    Name : String; 
    constructor Create(Name : String); 
    procedure Go(); 
end; 

procedure TOb.Go; 
begin 
    Form1.mmo1.Lines.Add(Name); 
end; 

そこで私は、このイベントとボタンがあります。

procedure TForm1.btn4Click(Sender: TObject); 
var 
    Index : Integer; 
begin 
    mmo1.Lines.Clear; 
    for Index := 1 to 3 do 
    TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(Index)).Go).Start; 
end; 

とメモの私の出力は次のとおりです。
スレッド4
スレッド4
スレッド4

私は本当にそれを持っていない。

最初の質問:なぜ「名前」の出力が次のようになりますか?スレッド4? 1〜3からのループのために少なくとも1または3

セカンドする必要がありますされています。それだけで順番に3回の代わりに、最後のスレッド「スレッド4」を実行なぜ「スレッド1」、「スレッド2」 、 "スレッド3"?

なぜ私はこれを求めていますか?私は既にプロセスが正常に動作しているオブジェクトを持っています。しかし、今私は、このオブジェクトのリストが処理される必要がある状況で私を見つけました。確かに1つずつ細かい作業をしていますが、私の場合は独立しているので、「うーん、スレッドに入れて、もっと速く走らせる」と考えました。私が代わりのTThreadから継承し、匿名スレッドを発見したオブジェクトの手順でスレッドを実行する方法について調べるを実行のTThreadとオーバーライドを拡張するためのオブジェクトを変更避けるため

。 1つのオブジェクトで本当にうまく動作しますが、オブジェクトリストをループすると奇妙な動作が発生します。

これは同じ効果があります。私は、オブジェクトを清掃しないよ

for Index := 1 to 3 do 
    TThread.CreateAnonymousThread(
     procedure 
     var 
     Ob : TOb; 
     begin 
     OB := TOb.Create('Thread ' + IntToStr(Index)); 
     OB.Go; 
     end 
    ).Start; 

確かに、これは私が実行していただけでいくつかのテストでした。 すべてのアイデア?または、この場合、TThreadから継承し、メソッドを上書きする必要がありますか?

このはうまく動作します。

mmo1.Lines.Clear; 
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(1)).Go).Start; 
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(2)).Go).Start; 
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(3)).Go).Start; 

出力:3

+0

これらすべての例は、メインのUIスレッドの外側から 'TMemo'にアクセスしているため、**未定義の動作**を示しています。したがって、結果はすべてランダムになり、予期しない問題が発生する可能性があります。あなたは 'TThread.Synchronize()'のようにメインのUIスレッドと同期する必要があります**。しかし、その場合でも、[匿名プロシージャーが変数にバインドする方法](http://docwiki.embarcadero.com/RADStudio/en/Anonymous_Methods_in_Delphi#Anonymous_Methods_Variable_Binding)を考慮する必要があります。 –

+0

私の場合、オブジェクトを持つObjectListはうまくいくでしょうか?私はビジュアルコンポーネントでデバッグしようとしていたので問題がありますか?この場合Formem1 with TMemo –

+0

おそらくそうではありません。そうでないと、最初にこの質問をしていないでしょう。 –

答えて

0


 スレッド1
 スレッド2
 スレッドは一つのオブジェクトと本当に素晴らしい作品が、私は私のオブジェクトリストをループをしようとしたとき、奇妙な行動が起こります。

how anonymous procedures bind to variablesを考慮していない可能性があります。特に:

注変数捕捉は変数 --not をキャプチャしていること。匿名メソッドを作成して変数の値を変更した場合、匿名メソッドが取得した変数の値も同じ記憶域を持つ変数であるため変更されます。キャプチャされた変数は、スタックではなくヒープに格納されます。例えば

、あなたはこのような何かを行う場合:

var 
    Index: Integer; 
begin 
    for Index := 0 to ObjList.Count-1 do 
    TThread.CreateAnonymousThread(TOb(ObjList[Index]).Go).Start; 
end; 

あなたが実際にスレッドでEListError例外を(私はそれをテストしたときに、私は少なくとも原因になります - それはなぜ起こるか私にはわかりません。 Start()を呼び出す前にスレッドにOnTerminateハンドラを割り当てて確認し、そのハンドラにTThread(Sender).FatalExceptionプロパティをチェックさせることで確認します)。

あなたの代わりにこれを行う場合:CreateAnonymousThread()TOb.Go()方法自体への参照を取っているので、

var 
    Index: Integer; 
    Ob: TOb; 
begin 
    for Index := 0 to ObjList.Count-1 do 
    begin 
    Ob := TOb(ObjList[Index]); 
    TThread.CreateAnonymousThread(Ob.Go).Start; 
    end; 
end; 

スレッドはもうクラッシュしませんが、彼らは同じTObオブジェクトを操作する可能性がある、とあなたのループは、各繰り返しで参照のSelfポインタを変更しています。あなたの代わりにこれを行う場合は

var 
    Index: Integer; 
    Ob: TOb; 
    Proc: TProc; // <-- silently added 
begin 
    for Index := 0 to ObjList.Count-1 do 
    begin 
    Ob := TOb(ObjList[Index]); 
    Proc := Ob.Go; // <-- silently added 
    TThread.CreateAnonymousThread(Proc).Start; 
    end; 
end; 

が、それは同様の問題があります:私は、コンパイラはおそらく、このようなコードを生成しているを疑う

procedure StartThread(Proc: TProc); 
begin 
    TThread.CreateAnonymousThread(Proc).Start; 
end; 

... 

var 
    Index: Integer; 
    Ob: TOb; 
begin 
    for Index := 0 to ObjList.Count-1 do 
    begin 
    Ob := TOb(ObjList[Index]); 
    StartThread(Ob.Go); 
    end; 
end; 

おそらくコンパイラが類似したコードを生成するので、これまで:

これはうまくいくでしょう:

procedure StartThread(Ob: TOb); 
begin 
    TThread.CreateAnonymousThread(Ob.Go).Start; 
end; 

... 

var 
    Index: Integer; 
    Ob: TOb; 
begin 
    for Index := 0 to ObjList.Count-1 do 
    begin 
    Ob := TOb(ObjList[Index]); 
    StartThread(Ob); 
    // or just: StartThread(TOb(ObjList[Index])); 
    end; 
end; 

ローカル変数にTOb.Go()への実際の参照を分離独立した手順にCreateAnonymousThread()への呼び出しを移動させることにより、あなたは複数のオブジェクトの参照を取得するには、競合の任意のチャンスを削除します。

匿名の手順は面白いです。彼らはどのように変数をキャプチャするかに注意する必要があります。

+0

haha​​ha、ありがとう、私は非常に似たような解決策を時間内にあなたの答えを見たことがなかった。私は本当に深いデルファイのものを見てみる必要がありますが、このメソッドのポインタや匿名のバインディングはそれほど単純ではありません。ありがとう。 –

+0

"CreateAnonymousThread()への呼び出しを別のプロシージャに移動することで、実際に問題が解決されます。 –

0

articleRemy Lebeau投稿を読んだところ、この解決策が見つかりました。

メインオブジェクトを変更するには、もう1つの手順を追加して呼び出しを変更します。 メインループで匿名スレッドを作成するのではなく、ループを変更します。ループはオブジェクト内に作成されます。

TOb = class 
    Name : String; 
    constructor Create(Name : String); 
    procedure Process(); 
    procedure DoWork(); 
end; 

procedure TOb.Process; 
begin 
    TThread.CreateAnonymousThread(DoWork).Start; 
end; 

procedure TOb.DoWork; 
var 
    List : TStringList; 
begin 
    List := TStringList.Create; 
    List.Add('I am ' + Name); 
    List.Add(DateTimeToStr(Now)); 
    List.SaveToFile('D:\file_' + Name + '.txt'); 
    List.Free; 
end; 

とループ:

List := TObjectList<TOb>.Create(); 
List.Add(TOb.Create('Thread_A')); 
List.Add(TOb.Create('Thread_B')); 
List.Add(TOb.Create('Thread_C')); 
List.Add(TOb.Create('Thread_D')); 

for Obj in List do 
    //TThread.CreateAnonymousThread(Obj.Go).Start; 
    Obj.Process; 

ザッツが主な目的でちょうど最小限の変更に伴う問題を解決します。