TParallel.For
performs a threaded execution of the iteration events, but itself is a blocking method. So for this you will have to be careful with the synchronization, if you start this from the main thread.
The use of TThread.Queue
works safely but as you already noticed, all the queued events are processed after TParallel.For
has finished - in fact, after leaving the method and return to idle.
The use of TThread.Synchronize
will cause a dead lock, if you use it in the iteration events and start TParallel.For
from the main thread.
Here is a little application showing the difference using
CopyFiles
ParallelCopyFiles
AsyncCopyFiles
calls CopyFiles
from a task
AsyncParallelCopyFiles
calls ParallelCopyFiles
from a task
And I assume AsyncParallelCopyFiles
is the one you are looking for.
Within the Async...
methods it is safe to use TThread.Synchronize
- if you do not wait for task inside the main thread.
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.
UPDATED
I just extend the log message with more information about the thread and the occurred time of the message