Copy file to stream

I am trying to write to copy a file by calling a separate thread. Here is my form code:

unit frmFileCopy; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, StdCtrls; type TForm2 = class(TForm) Button3: TButton; procedure Button3Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); private ThreadNumberCounter : integer; procedure HandleTerminate (Sender: Tobject); end; var Form2: TForm2; implementation uses fileThread; {$R *.dfm} { TForm2 } const sourcePath = 'source\'; //' destPath = 'dest\'; //' fileSource = 'bigFile.zip'; fileDest = 'Copy_bigFile.zip'; procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin CanClose := true; if ThreadNumberCounter >0 then begin if MessageDlg('The file is being copied. Do you want to quit?', mtWarning, [mbYes, mbNo],0) = mrNo then CanClose := false; end; end; procedure TForm2.FormCreate(Sender: TObject); begin ThreadNumberCounter := 0; end; procedure TForm2.Button3Click(Sender: TObject); var sourceF, destF : string; copyFileThread : TCopyThread; begin sourceF := ExtractFilePath(ParamStr(0)) + sourcePath + fileSource; destF := ExtractFilePath(ParamStr(0)) + sourcePath + fileDest; copyFileThread := TCopyThread.create(sourceF,destF); copyFileThread.FreeOnTerminate := True; try Inc(ThreadNumberCounter); copyFileThread.Execute; copyFileThread.OnTerminate := HandleTerminate; copyFileThread.Resume; except on Exception do begin copyFileThread.Free; ShowMessage('Error in thread'); end; end; end; procedure TForm2.HandleTerminate(Sender: Tobject); begin Dec(ThreadNumberCounter); end; 

Here is my class:

 unit fileThread; interface uses Classes, SysUtils; type TCopyThread = class(TThread) private FIn, FOut : string; procedure copyfile; public procedure Execute ; override; constructor create (const source, dest : string); end; implementation { TCopyThread } procedure TCopyThread.copyfile; var streamSource, streamDest : TFileStream; bIn, bOut : byte; begin streamSource := TFileStream.Create(FIn, fmOpenRead); try streamDest := TFileStream.Create(FOut,fmCreate); try streamDest.CopyFrom(streamSource,streamSource.Size); streamSource.Position := 0; streamDest.Position := 0; {check file consinstency} while not (streamSource.Position = streamDest.Size) do begin streamSource.Read(bIn, 1); streamDest.Read(bOut, 1); if bIn <> bOut then raise Exception.Create('files are different at position' + IntToStr(streamSource.Position)); end; finally streamDest.Free; end; finally streamSource.Free; end; end; constructor TCopyThread.create(const source, dest: string); begin FIn := source; FOut := dest; end; procedure TCopyThread.Execute; begin copyfile; inherited; end; end. 

When I launch the application, I received the following error:

Project prjFileCopyThread an EThread exception class with the message: "Unable to call" Start a running or paused thread. "

I have no experience with threads. I use the Martin Harvey tutorial as a guide, but any advice on how to improve it, make a safe flow will be appreciated.


Based on the answers, I changed my code. This time it worked. I would appreciate it if you could review it again and tell what should be improved.

 procedure TForm2.Button3Click(Sender: TObject); var sourceF, destF : string; copyFileThread : TCopyThread; begin sourceF := ExtractFilePath(ParamStr(0)) + sourcePath + fileSource; destF := ExtractFilePath(ParamStr(0)) + destPath + fileDest; copyFileThread := TCopyThread.create; try copyFileThread.InFile := sourceF; copyFileThread.OutFile := destF; except on Exception do begin copyFileThread.Free; ShowMessage('Error in thread'); end; end; 

Here is my class:

 type TCopyThread = class(TThread) private FIn, FOut : string; procedure setFin (const AIN : string); procedure setFOut (const AOut : string); procedure FCopyFile; protected procedure Execute ; override; public constructor Create; property InFile : string write setFin; property OutFile : string write setFOut; end; implementation { TCopyThread } procedure TCopyThread.FCopyfile; var streamSource, streamDest : TFileStream; bIn, bOut : byte; begin {removed the code to make it shorter} end; procedure TCopyThread.setFin(const AIN: string); begin FIn := AIN; end; procedure TCopyThread.setFOut(const AOut: string); begin FOut := AOut; end; constructor TCopyThread.create; begin FreeOnTerminate := True; inherited Create(FALSE); end; procedure TCopyThread.Execute; begin FCopyfile; end; end. 
+2
source share
5 answers

Just for comparison, how you do it with the OmniThreadLibrary .

 uses OtlCommon, OtlTask, OtlTaskControl; type TForm3 = class(TForm) ... FCopyTask: IOmniTaskControl; end; procedure BackgroundCopy(const task: IOmniTask); begin CopyFile(PChar(string(task.ParamByName['Source'])), PChar(string(task.ParamByName['Dest'])), true); //Exceptions in CopyFile will be mapped into task exit status end; procedure TForm3.BackgroundCopyComplete(const task: IOmniTaskControl); begin if task.ExitCode = EXIT_EXCEPTION then ShowMessage('Exception in copy task: ' + task.ExitMessage); FCopyTask := nil; end; procedure TForm3.Button3Click(Sender: TObject); begin FCopyTask := CreateOmniTask(BackgroundCopy) .SetParameter('Source', ExtractFilePath(ParamStr(0)) + sourcePath + fileSource) .SetParameter('Dest', ExtractFilePath(ParamStr(0)) + destPath + fileDest) .SilentExceptions .OnTerminate(BackgroundCopyComplete) .Run; end; procedure TForm3.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin CanClose := true; if assigned(FCopyTask) then begin if MessageDlg('The file is being copied. Do you want to quit?', mtWarning, [mbYes, mbNo],0) = mrNo then CanClose := false else FCopyTask.Terminate; end; end; 
+6
source

You have a few problems:

  • You do not invoke the inherited Create . In this case, since you want to do something first and start it yourself, you should use

    inherited Create (True); // Creates a new thread.

  • You should never call Execute yourself. It is automatically called if you create not paused or you call Resume .

  • There is no Inherited Execute , but you name it anyway.

BTW, you can also use the built-in Windows Shell SHFileOperation function to make a copy. It will run in the background, processes several files and wildcards, and can automatically display progress to the user. You can probably find an example of using it in Delphi here on SO; here is a link to use it to recursively delete files, for example.

Good search here on SO (without quotes) shfileoperation [delphi]

+9
source

Your edited code still has at least two big problems:

  • You have a constructor without parameters, and then specify the names of the sources and destination files using the properties of the stream class. Everything that you have been told about creating suspended threads that are not necessary is only true if you perform all the settings in the thread designer - after that the thread will end and you will need to synchronize access to the properties of the stream. You should (like your first version of the code) give both names as parameters for the stream. This is even worse: the only safe way to use a stream with the FreeOnTerminate property FreeOnTerminate is to not access any properties after the constructor completes, because the stream may have already destroyed itself or could have done so long as the property is available.

  • In the event of an exception, you free the thread object, even if you set its FreeOnTerminate property. This is likely to result in a double free exception from the memory manager.

I also wonder how you want to know when the file copy will be completed - if there is no exception, the button click handler will exit with the thread still running in the background. There is also no means to cancel the working thread. This will force your application to exit only after the stream completes.

In general, you would be better off using one of the Windows file copy routines with cancellation and callbacks, as Ken noted in his answer .

If you do this only for experimenting with streams - do not use file operations for your tests, they are not suitable for several reasons, not only because there are better ways in the main thread to do the same, but also because I / O bandwidth will be used best if no parallel operations are undertaken (this means: do not try to copy multiple files in parallel, creating several of your streams).

+3
source

The Execute method of a thread is usually not explicitly called by client code. In other words: delete CopyFileThread.Execute in the frmFileCopy module. The thread starts when the Resume method is called.

Also in the fileThread module in the TCopyThread constructor inherited by Create (True), it must be called as the first to create a thread in a suspended state.

+2
source

You execute a thread and then try to resume it while it starts.

 copyFileThread.Execute; copyFileThread.OnTerminate := HandleTerminate; copyFileThread.Resume; 
0
source

Source: https://habr.com/ru/post/902029/


All Articles