Cara menyinkronkan TParallell di Delphi XE7 untuk mencatat data

Saya perlu mencatat beberapa data dan sebaiknya menyalin file menggunakan utas, tetapi dengan menggunakan kode di bawah ini, tetapi itu hanya membekukan aplikasi saya.

jika saya memahami seluruh perpustakaan Paralel XE7 dengan benar TThread.Queue dan TThread.Synchronize seharusnya disinkronkan dengan utas utama, tetapi dalam kasus saya seluruh aplikasi terhenti.

Apa yang saya lakukan salah?

procedure TCopyDeviceContent.StartCopy;
var
  OK: boolean;
begin
  OK := false;

  //  showmessage('fFiles.Count = '+inttostr(fFiles.Count));

  if fFiles.Count = 0 then
  begin
    NotifyDone;
    exit;
  end;

  TParallel.For(0, fFiles.Count-1,
    procedure (Value: Integer)
    begin
      TThread.Queue(TThread.CurrentThread, //Here is where it freezes
        procedure
        begin
          Log('Setting fCurrentFile to '+fFiles.Strings[value]);
        end
      );

      sleep(1000);

      fCurrentFile := fFiles.Strings[value];
      Log('Triggering fOnBeforeProcess');
      if assigned(fOnBeforeProcess) then fOnBeforeProcess(self);

      Log('Does file exist?');
      if FileExists(fFiles.Strings[value]) = true then
      begin
        Log('Yes!');
        Log('Trying to copy file to Windows temp folder.');
        try
          TFile.Copy(fFiles.Strings[value], GetEnvironmentVariable('TEMP'));
        finally
          OK := true;
        end;

        if OK = true then
        begin
          Log('Success!');
          OK := false;

          Log('Does file exist in Windows temp folder?');
          if FileExists(GetEnvironmentVariable('TEMP')+ExtractFileName(fFiles.Strings[value])) then
          begin
            Log('Yes');
            Log('Trying to copy file from Windows temp folder to final destination: '+DestPath+DateToStr(Now)+'\'+ExtractFileName(fFiles.Strings[value]));
            try
              TFile.Move(GetEnvironmentVariable('TEMP')+ExtractFileName(fFiles.Strings[value]),
              DestPath+DateToStr(Now)+'\'+ExtractFileName(fFiles.Strings[value]));
            finally
              fFilesOK.Add(fFiles.Strings[value]);
              Log('Sucess!');
            end;
          end;    
        end
        else
        begin
          fFilesFailed.Add(fFiles.Strings[value]);
          Log('Failed copying to Windows temp folder!');
        end;
      end;
      inc(fProgress);
      NotifyProgress;
      Log('File copy success. Moving on to next file if available...');
    end
  );

  NotifyDone;

  if fFilesFailed.Count > 0 then NotifyError;
end;

person Aid Vllasaliu    schedule 10.03.2015    source sumber
comment
Jangan lewati utas saat memanggil TThread.Queue. Gunakan panggilan kelebihan beban tanpa opsi ini. Lihat Menggunakan Perpustakaan Paralel Delphi XE7.   -  person LU RD    schedule 10.03.2015
comment
Koreksi: kode di dalam TThread.Queue dieksekusi SETELAH thread selesai. TThread.Synchronizeadalah apa yang saya perlukan untuk implementasi saya.   -  person Aid Vllasaliu    schedule 10.03.2015
comment
@LURD tidak ada panggilan kelebihan beban tanpa opsi. Di mana saya menemukannya?   -  person Aid Vllasaliu    schedule 10.03.2015
comment
TThread.Queue(nil, Aproc); seharusnya berfungsi.   -  person LU RD    schedule 10.03.2015
comment
TThread.Queue dengan nil yang ditetapkan ke parameter pertama berfungsi, tetapi setelah membaca lebih lanjut tentang ini, ternyata kode apa pun di dalam TThread.Queue dieksekusi setelah thread selesai. Saya tidak memerlukannya, saya perlu mencatat setiap langkah proses yang sedang terjadi, oleh karena itu mengapa TThread.Synchronize lebih cocok.   -  person Aid Vllasaliu    schedule 10.03.2015
comment
@LURD ngomong-ngomong, apa itu Aproc?   -  person Aid Vllasaliu    schedule 10.03.2015
comment
Ini adalah kependekan dari prosedur anonim.   -  person LU RD    schedule 10.03.2015
comment
kode di dalam TThread.Queue dieksekusi SETELAH thread selesai. Belum tentu benar. Kode akan dieksekusi pada akhirnya, dokumen tidak pernah menyatakan apa yang saya katakan.   -  person EProgrammerNotFound    schedule 10.03.2015
comment
@EProgrammerNotFound Anda benar!   -  person Aid Vllasaliu    schedule 10.03.2015
comment
@AidVllasaliu mengatakan bahwa kode di TThread.Queue dieksekusi setelah thread selesai jelas salah.   -  person iamjoosy    schedule 10.03.2015
comment
Kode yang diteruskan ke TThread.Queue() dieksekusi setiap kali thread utama mengeksekusinya. Ini tidak ada hubungannya dengan masa pakai thread yang memanggil Queue(). Thread mungkin masih berjalan, atau mungkin sudah dihentikan. Intinya Queue() asynchronous, langsung keluar dan tidak menunggu kode dieksekusi. TThread.Synchronize() menunggu.   -  person Remy Lebeau    schedule 10.03.2015
comment
Apa yang dilakukan prosedur Log()? Memperbarui UI, menulis ke file?   -  person iPath ツ    schedule 10.03.2015
comment
Dalam kasus khusus ini acara antrian dieksekusi setelah TParallel.For hanya karena ini merupakan operasi pemblokiran dan menunggu hingga semuanya selesai. TThread.Sychronize seharusnya menyebabkan kebuntuan di sini   -  person Sir Rufo    schedule 10.03.2015
comment
@iPathツ log() cukup menambahkan sebaris string ke TMemo   -  person Aid Vllasaliu    schedule 10.03.2015
comment
@AidVllasaliu apakah metode Log() menggunakan TThread.Synchronize/queue?   -  person iPath ツ    schedule 10.03.2015
comment
@iPathツ Lihat jawaban saya, yang menjelaskan mengapa pesan antrian diproses setelahnya   -  person Sir Rufo    schedule 10.03.2015
comment
@iPathツ tidak, metode Log() terletak di thread utama. sebenarnya dalam unit yang benar-benar berbeda.   -  person Aid Vllasaliu    schedule 11.03.2015
comment
@AidVllasaliu metode seperti metode Log() Anda tidak dapat ditemukan di thread utama. Itu bisa dipanggil dari thread mana pun termasuk thread utama. Yaitu dieksekusi dalam konteks thread. Dalam contoh Anda, Anda terutama memanggil metode Log() dari konteks thread untuk pekerja saat ini dan bukan dari thread utama. Satu-satunya cara untuk memanggilnya dalam konteks Thread Utama adalah dengan menggunakan metode Sinkronisasi/Antrian. Satu-satunya perbedaan antara kedua metode ini adalah Sinkronisasi adalah fungsi pemblokiran dan Antrian bukan fungsi pemblokiran.   -  person iPath ツ    schedule 11.03.2015
comment
@iPathツ Baiklah, yang saya maksud adalah kode saya yang berfungsi sekarang, terima kasih kepada Edin dan LURD. Jawaban mereka berfungsi dengan baik, dan saya telah mengganti baris Form1.Memo1..... mereka dengan metode Log() saya.   -  person Aid Vllasaliu    schedule 11.03.2015


Jawaban (2)


Jika tujuannya hanya untuk menyalin file tanpa membekukan thread UI saya hanya akan menggunakan sesuatu seperti ini:

procedure TCopyDeviceContent.StartCopy;
var
 aTask: ITask;
begin
 aTask := TTask.Create (procedure ()
   begin
      // Copy files here  
      TThread.Synchronize(nil,procedure
                  begin
                     //Interact with UI  
                     Form1.Memo1.Lines.Add(‘Begin Execution’);
                  end);
   end);
 aTask.Start;
end;

Di dalam prosedur tugas, cukup salin file seperti biasanya, saya tidak yakin apakah menyalin menggunakan banyak utas akan membantu Anda.

Jika Anda perlu berinteraksi dengan UI, Anda perlu beralih kembali ke thread UI, Anda dapat menggunakan TThread.Synchronize.

person Edin Omeragic    schedule 10.03.2015

TParallel.For melakukan eksekusi thread dari peristiwa iterasi, namun itu sendiri merupakan metode pemblokiran. Jadi untuk ini Anda harus berhati-hati dengan sinkronisasi, jika Anda memulainya dari thread utama.

Penggunaan TThread.Queue bekerja dengan aman tetapi seperti yang sudah Anda ketahui, semua kejadian yang diantri diproses setelah TParallel.For selesai - sebenarnya, setelah keluar dari metode dan kembali ke idle.

Penggunaan TThread.Synchronize akan menyebabkan kebuntuan, jika Anda menggunakannya dalam acara iterasi dan memulai TParallel.For dari thread utama.

Berikut adalah sedikit aplikasi yang menunjukkan perbedaan penggunaannya

  • CopyFiles
  • ParallelCopyFiles
  • AsyncCopyFiles memanggil CopyFiles dari suatu tugas
  • AsyncParallelCopyFiles memanggil ParallelCopyFiles dari suatu tugas

Dan saya berasumsi AsyncParallelCopyFiles adalah yang Anda cari.

Dalam metode Async... aman untuk menggunakan TThread.Synchronize - jika Anda tidak menunggu tugas di dalam thread utama.

unit Form.Main;

interface

uses
  System.IOUtils,
  System.Threading,
  System.Types,

  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TLogMsg = record
  private
    FMsg: string;
    FThreadID: Cardinal;
    FOccurred: TDateTime;
  public
    class operator implicit( a: string ): TLogMsg;
    class operator implicit( a: TLogMsg ): string;

    constructor Create( const AMsg: string );
    function ToString: string;

    property Msg: string read FMsg;
    property ThreadID: Cardinal read FThreadID;
    property Occurred: TDateTime read FOccurred;
  end;

type
  TForm1 = class( TForm )
    ListBox1: TListBox;
    RadioGroup1: TRadioGroup;
    Button1: TButton;
    procedure Button1Click( Sender: TObject );
  private
    FTask: ITask;
    procedure ThreadSafeLog( ALogMsg: TLogMsg );
  public
    procedure CopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean );
    procedure ParallelCopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean );

    function AsyncCopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean; ACallback: TThreadProcedure ): ITask;
    function AsyncParallelCopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean; ACallback: TThreadProcedure ): ITask;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
{ TForm1 }

// *** ATTENTION ***
// ParallelCopyFiles will cause a dead lock without USE_QUEUE
// but you still can try yourself ...
//
{$DEFINE USE_QUEUE}
//
// *****************

procedure TForm1.ThreadSafeLog( ALogMsg: TLogMsg );
begin
{$IFDEF USE_QUEUE}
  TThread.Queue
{$ELSE}
  TThread.Synchronize
{$ENDIF}
    ( nil,
      procedure
    begin
      ListBox1.Items.Add( ALogMsg );
    end );
end;

procedure TForm1.CopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean );
var
  LSource, LDestination: string;
begin
  ThreadSafeLog( 'CopyFiles - ENTER' );
  for LSource in AFiles do
    begin
      LDestination := TPath.Combine( ADestPath, TPath.GetFileName( LSource ) );
      ThreadSafeLog( 'Copy ' + LSource );
      TFile.Copy( LSource, LDestination, Overwrite );
    end;
  ThreadSafeLog( 'CopyFiles - EXIT' );
end;

procedure TForm1.ParallelCopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean );
begin
  ThreadSafeLog( 'ParallelCopyFiles - ENTER' );
  TParallel.&For( Low( AFiles ), High( AFiles ),
    procedure( AIndex: Integer )
    var
      LSource, LDestination: string;
    begin
      LSource := AFiles[AIndex];
      LDestination := TPath.Combine( ADestPath, TPath.GetFileName( LSource ) );
      ThreadSafeLog( 'Copy ' + LSource );
      TFile.Copy( LSource, LDestination, Overwrite );
    end );
  ThreadSafeLog( 'ParallelCopyFiles - EXIT' );
end;

function TForm1.AsyncCopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean; ACallback: TThreadProcedure ): ITask;
begin
  ThreadSafeLog( 'AsyncCopyFiles - ENTER' );
  Result := TTask.Run(
    procedure
    begin
      CopyFiles( AFiles, ADestPath, Overwrite );
      TThread.Synchronize( nil, ACallback );
    end );
  ThreadSafeLog( 'AsyncCopyFiles - EXIT' );
end;

function TForm1.AsyncParallelCopyFiles( AFiles: TStringDynArray; ADestPath: string; Overwrite: Boolean; ACallback: TThreadProcedure ): ITask;
begin
  ThreadSafeLog( 'AsyncParallelCopyFiles - ENTER' );
  Result := TTask.Run(
    procedure
    begin
      ParallelCopyFiles( AFiles, ADestPath, Overwrite );
      TThread.Synchronize( nil, ACallback );
    end );
  ThreadSafeLog( 'AsyncParallelCopyFiles - EXIT' );
end;

procedure TForm1.Button1Click( Sender: TObject );
var
  LFiles: TStringDynArray;
  LDestPath: string;
begin
  ListBox1.Clear; // Clear the log destination

  LFiles := TDirectory.GetFiles( TPath.GetDocumentsPath, '*.*' );
  LDestPath := TPath.Combine( TPath.GetDocumentsPath, '_COPYTEST_' );
  TDirectory.CreateDirectory( LDestPath );

  case RadioGroup1.ItemIndex of
    0:
      CopyFiles( LFiles, LDestPath, True );
    1:
      ParallelCopyFiles( LFiles, LDestPath, True );
    2:
      begin
        Button1.Enabled := False;
        AsyncCopyFiles( LFiles, LDestPath, True,
          procedure
          begin
            Button1.Enabled := True;
          end );
      end;
    3:
      begin
        Button1.Enabled := False;
        AsyncParallelCopyFiles( LFiles, LDestPath, True,
          procedure
          begin
            Button1.Enabled := True;
          end );
      end;
  end;
end;

{ TLogMsg }

constructor TLogMsg.Create( const AMsg: string );
begin
  FMsg := AMsg;
  FThreadID := TThread.CurrentThread.ThreadID;
  FOccurred := Now;
end;

class operator TLogMsg.implicit( a: string ): TLogMsg;
begin
  Result := TLogMsg.Create( a );
end;

class operator TLogMsg.implicit( a: TLogMsg ): string;
begin
  Result := a.ToString;
end;

function TLogMsg.ToString: string;
begin
  Result := Format( '$%8.8x [%s] %s', [FThreadID, FormatDateTime( 'hh:nn:ss.zzz', FOccurred ), FMsg] );
end;

end.

DIPERBARUI

Saya baru saja menyampaikan pesan log dengan informasi lebih lanjut tentang thread dan waktu terjadinya pesan

person Sir Rufo    schedule 10.03.2015
comment
Apakah Anda yakin tentang kebuntuan jika Parallel.For dimulai dari thread utama dengan panggilan untuk sinkronisasi? Saya belum pernah menggunakan perpustakaan threading, tetapi implementasi saya sendiri menempatkan overhead Parallel.For di threadnya sendiri. - person LU RD; 10.03.2015
comment
@LURD Itulah alasan peralihan kompiler. Anda akan menemui jalan buntu dan saya akan sangat terkejut jika tidak. Bagaimana Anda bisa menyinkronkan thread dengan thread utama jika thread utama menunggu thread ini selesai? Anda tidak bisa - person Sir Rufo; 11.03.2015
comment
@LURD TParallel.ForWorker di PPL membuat tugas terpisah untuk pekerjaan sebenarnya tetapi diakhiri dengan RootTask.Start.Wait(). Jadi TParallel.For adalah fungsi pemblokiran. - person iPath ツ; 11.03.2015
comment
@SirRufo +100 untuk penjelasan dan contohnya. Saya pikir posting Anda harus dipilih sebagai jawaban :) - person iPath ツ; 11.03.2015
comment
@iPathツ, ya, saya mencarinya dan mengonfirmasi kebuntuan. Saya mengharapkan menunggu dengan panggilan ke CheckSynchronize atau serupa untuk menghindari kebuntuan, tetapi tidak ada hal seperti itu. - person LU RD; 11.03.2015
comment
Terima kasih atas penjelasan lengkap yang dikomentari tentang mengapa saya mengalami kebuntuan di loop tidur TParallel.For saya ^^ - person Darkendorf; 24.12.2015
comment
@SirRufo, Anda telah ThreadSafeLog didefinisikan sebagai procedure TForm1.ThreadSafeLog( ALogMsg: TLogMsg );. Kemudian nanti Anda menyebutnya sebagai ThreadSafeLog( 'ParallelCopyFiles - EXIT' );. Apakah itu dikompilasi? - person Interface Unknown; 03.07.2017
comment
@InterfaceUnknown Ya, itu dapat dikompilasi - person Sir Rufo; 03.07.2017
comment
@SirRufo, terima kasih. Saya mungkin melewatkan sesuatu... Akan mempelajari kodenya lebih dalam. - person Interface Unknown; 03.07.2017
comment
@InterfaceUnknown Keajaiban akan terjadi karena operator implisit TLogMsg. - person Sir Rufo; 03.07.2017