![]() |
CopyFile lässt die Anwendung hängen, wie umgehen?
Hallo,
ich lasse mit CopyFile Dateien kopieren. Während des Kopiervorhangs friert die Anwendung ein. Wie kann man das vermeiden? Den Kopiervorgang in ein extra Thread packen, oder gibt's da auch andere Möglichkeiten? Danke! |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Was spricht gegen den Thread? wäre ein weiterer Anwendungsfall für den
![]() |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Oder
![]() |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Die ShellApi. Siehe:
![]() Aber ich könnte mir vorstellen, dass es die Funktion in Lazarus gar nicht gibt. Ein Thread wäre auf jeden Fall der richtige Weg. Seit Delphi XE7 geht das ja spielend einfach. Wie es bei Lazarus aussieht, kann auch hier nicht beurteilen. Grüße |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Was ich damit sagen will: Ich hab noch nie CopyFile oder CopyFileEx in einer fertigen Anwendung verwendet. |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Unit Windows? :roll: (zumindestens im Delphi, weil es ja aus der WinAPI kommt)
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Delphi-Quellcode:
und in wiefern lässt es die Anwendung nicht einfrieren?
Application.ProcessMessages
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Delphi-Quellcode:
aufrufen? Ok, muss ma ausprobieren.
Application.ProcessMessages
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Es gibt aber keine Gewähr, dass das richti funktioniert. Besser ist es die Arbeit in einen Hintergrundprozess auzulagern. Der Hauptthread kümmert sich dann nur um die Anzeige des Fortschritts.
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Application.ProcessMessages verarbeitet "jetzt" anstehende Messages in der Message-Queue des Hauptthreads, was so in etwa ganz bestimmt auch in der Hilfe erklärt wird. Und da Timer-Ereignisse ja bekanntlich über eine Message ausgelöst werden... |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Code:
Achtung:
function CopyFileEx(lpExistingFileName, lpNewFileName: PChar;
lpProgressRoutine: Pointer; lpData: Pointer; pbCancel: PBool; dwCopyFlags: DWORD): WINBOOL; external 'kernel32' name 'FileCopyExA'; Parameter nicht überprüft. Eventuell auch noch für 'FileCopyExW' erstellen |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Warum willst Du den keinen Thread nehmen... Ich überlege jeden Tag: "Kann ich diese 3 Zeilen im Thread ausführen?" Und wenn es dann endlich mal etwas ist, was nicht im Hauptthread laufen muss, freue ich mich ein Schneekönig. Mavarik |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Ich hab eine Routine zum Files kopieren, die würde ich auch gerne ab und zu mal in einen Thread packen. Das Problem - aus meiner Sicht - ist die Synchronisation des ProgressBar im Formular. So sieht es momentan aus:
Delphi-Quellcode:
Womit dem TE ja auch geholfen wäre ist die Frage: Wie packt man das in einen Thread?
...
type TFileCopyMode = (fomAlways, fomIfNewer, fomIfOlder, fomIfSameDate, fomIfSameDateTime); TFileCopyCallback = procedure(ProgressValue: Integer) of object; ... function MyCopyFile(SourceName, DestName: string; FileCopyMode: TFileCopyMode; Progress: TFileCopyCallback): string; var BytesRead, BytesToRead, P, Percent: Int64; SourceStream, DestStream: TFileStream; SourceDateTime, DestDateTime: TDateTime; begin Result := ''; if not FileExists(SourceName) then Exit('Quelldatei <'+SourceName+'> existiert nicht'); if not FileAge(SourceName, SourceDateTime) then Exit('Quelldatei: TimeStamp lesen fehlgeschlagen'); BytesRead := 0; Percent := 0; P := 0; // DestName darf auch ein Directory sein...dann den Dateinamen ran hängen if DirectoryExists(DestName) // DestName ist ein Directory then DestName := IncludeTrailingBackslash(DestName)+ExtractFileName(SourceName); if FileExists(DestName) then begin if not FileAge(DestName, DestDateTime) then Exit('Zieldatei: TimeStamp lesen fehlgeschlagen'); case FileCopyMode of fomAlways :;// immer kopieren fomIfNewer : // Die Quelldatei muss neuer als die Zieldatei sein if NOT (SourceDateTime > DestDateTime) then Exit('Die Quelldatei ist nicht neuer als die Zieldatei'); fomIfOlder : // Die Quelldatei muss älter als die Zieldatei sein => z.B. Downgrade if NOT (SourceDateTime < DestDateTime) then Exit('Die Quelldatei ist nicht älter als die Zieldatei'); fomIfSameDate : // Quell- und Ziel-Datei müssen am selben Tag erstellt worden sein if Trunc(DestDateTime) <> Trunc(SourceDateTime) then Exit('Die Zieldatei ist nicht vom selben Datum als die Quelldatei'); fomIfSameDateTime : // Das Datum von Quell- und Ziel-Datei muss identisch sein if DestDateTime <> SourceDateTime then Exit('Datum und Zeit von Zieldatei und Quelldatei sind unterschiedlich'); else raise Exception.Create('Da hat der Programmierer was vergessen'); end; end; SourceStream := TFileStream.Create(SourceName,fmOpenRead or fmShareDenyNone); DestStream := TFileStream.Create(DestName,fmCreate); try if @Progress = nil then DestStream.CopyFrom(SourceStream,SourceStream.Size) // so geht es am schnellsten else begin // mit Fortschrittanzeige Progress(0); BytesToRead := SizeOf(StreamCopyBuffer); if SourceStream.Size < BytesToRead then BytesToRead := SourceStream.Size; repeat SourceStream.ReadBuffer(StreamCopyBuffer,BytesToRead); DestStream.WriteBuffer(StreamCopyBuffer,BytesToRead); BytesRead := BytesRead + BytesToRead; Percent := (BytesRead * 100) div SourceStream.Size; if P <> Percent then begin Progress(Integer(Percent)); P := Percent; end; if (SourceStream.Size - BytesRead) <= BytesToRead then BytesToRead := SourceStream.Size - BytesRead; until BytesRead >= SourceStream.Size; if BytesRead <> SourceStream.SIZE then Result := 'Fehler beim Kopiervorgang:'+#13#10 +'Bytes gelesen:' + IntToStr(BytesRead) + #13#10 +'Bytes geschrieben:' + IntToStr(SourceStream.SIZE) else Progress(100); end; finally SourceStream.Free; DestStream.Free; end; if Result = '' then MySetAllFileDates(DestName, SourceDateTime, SourceDateTime, SourceDateTime); end; |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Du leitest von TThread ab und rufst dort in execute die Funktion auf. Das Callback ist dabei eine Methode in deiner Thread-Klasse, die das eigentliche Callback (zum Beispiel das Updaten der Progressbar) synchronisiert, dh. im Mainthread, aufruft. Die Parameter für die Funktion kannst du dem Thread im Konstruktor übergeben (der wird noch synchron im Mainthread ausgeführt), für die Rückgabewerte und Exceptions musst du dir noch was überlegen. Ein weiteres synchronisiertes Callback würde sich dafür anbieten. |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Hallo Robert,
danke für den Tipp. Hab's einfach mal probiert....und langsam verliere ich auch die Angst vor den Threads. Weil, es scheint auf Anhieb zu funktionieren. Für den TE:
Delphi-Quellcode:
Man kann während des Kopiervorganges das MainFormular beliebig bewegen, da friert absolut nichts ein und auch der ProgressBar geht schön durch.
unit RuFileCopy;
interface uses System.Classes; type TFileCopyMode = (fomAlways, fomIfNewer, fomIfOlder, fomIfSameDate, fomIfSameDateTime); TFileCopyCallback = procedure(ProgressPercent: Integer) of object; TRuFileCopyThread = class(TThread) private { Private-Deklarationen } FCopyResult, FSourceName, FDestName: string; FFileCopyMode: TFileCopyMode; FFileCopyCallback: TFileCopyCallback; procedure ThreadCopyCallback(ProgressPercent: Integer); protected procedure Execute; override; public constructor Create(aSourceName, aDestName: string; aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback); procedure ThreadIsReady(Sender: TObject); end; const cRuFileCopyReady = 200; var RuFileCopyThreadResult: string = ''; {- Eine Datei kopieren: Direkt-Aufruf -} function RuFileCopyExecute(aSourceName, aDestName: string; aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback): string; {- Eine Datei kopieren: Kopiert die Datei in einem eigenen Thread -} function RuFileCopyThread(aSourceName, aDestName: string; aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback): string; implementation uses System.SysUtils; var StreamCopyBuffer: array[0..500 * 1024] of byte; ProgressPercent: Int64; {------------------------------------------------------------------------------} {- Eine Datei kopieren: Direkt-Aufruf oder Aufruf aus dem Thread -} {- aSourceName = Name der Quelldatei -} {- aDestName = Name der ZielDatei, darf auch das Zielverzeichnis sein -} {- aFileCopyCallback = ProgressCallback: Übergibt Wert in Prozent von 1-100 -} {------------------------------------------------------------------------------} function RuFileCopyExecute(aSourceName, aDestName: string; aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback): string; var BytesRead, BytesToRead, P: Int64; SourceStream, DestStream: TFileStream; SourceDateTime, DestDateTime: TDateTime; begin Result := ''; if not FileExists(aSourceName) then Exit('Quelldatei <'+aSourceName+'> existiert nicht'); if not FileAge(aSourceName, SourceDateTime) then Exit('Quelldatei: TimeStamp lesen fehlgeschlagen'); BytesRead := 0; ProgressPercent := 0; P := 0; // DestName darf auch ein Directory sein...dann den Dateinamen ran hängen if DirectoryExists(aDestName) // DestName ist ein Directory then aDestName := IncludeTrailingBackslash(aDestName)+ExtractFileName(aSourceName); if FileExists(aDestName) then begin if not FileAge(aDestName, DestDateTime) then Exit('Zieldatei: TimeStamp lesen fehlgeschlagen'); case aFileCopyMode of fomAlways :;// immer kopieren fomIfNewer : // Die Quelldatei muss neuer als die Zieldatei sein if NOT (SourceDateTime > DestDateTime) then Exit('Die Quelldatei ist nicht neuer als die Zieldatei'); fomIfOlder : // Die Quelldatei muss älter als die Zieldatei sein => z.B. Downgrade if NOT (SourceDateTime < DestDateTime) then Exit('Die Quelldatei ist nicht älter als die Zieldatei'); fomIfSameDate : // Quell- und Ziel-Datei müssen am selben Tag erstellt worden sein if Trunc(DestDateTime) <> Trunc(SourceDateTime) then Exit('Die Zieldatei ist nicht vom selben Datum als die Quelldatei'); fomIfSameDateTime : // Das Datum von Quell- und Ziel-Datei muss identisch sein if DestDateTime <> SourceDateTime then Exit('Datum und Zeit von Zieldatei und Quelldatei sind unterschiedlich'); else raise Exception.Create('Da hat der Programmierer was vergessen'); end; end; SourceStream := TFileStream.Create(aSourceName,fmOpenRead or fmShareDenyNone); DestStream := TFileStream.Create(aDestName,fmCreate); try if @aFileCopyCallback = nil then DestStream.CopyFrom(SourceStream,SourceStream.Size) // so geht es am schnellsten else begin // mit Fortschrittanzeige aFileCopyCallback(0); BytesToRead := SizeOf(StreamCopyBuffer); if SourceStream.Size < BytesToRead then BytesToRead := SourceStream.Size; repeat SourceStream.ReadBuffer(StreamCopyBuffer,BytesToRead); DestStream.WriteBuffer(StreamCopyBuffer,BytesToRead); BytesRead := BytesRead + BytesToRead; ProgressPercent := (BytesRead * 100) div SourceStream.Size; if P <> ProgressPercent then begin aFileCopyCallback(Integer(ProgressPercent)); P := ProgressPercent; end; if (SourceStream.Size - BytesRead) <= BytesToRead then BytesToRead := SourceStream.Size - BytesRead; until BytesRead >= SourceStream.Size; if BytesRead <> SourceStream.SIZE then Result := 'Fehler beim Kopiervorgang:'+#13#10 +'Bytes gelesen:' + IntToStr(BytesRead) + #13#10 +'Bytes geschrieben:' + IntToStr(SourceStream.SIZE) else aFileCopyCallback(100); // Kopiervorgang 100% end; finally SourceStream.Free; DestStream.Free; end; end; function RuFileCopyThread(aSourceName, aDestName: string; aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback): string; begin with TRuFileCopyThread.Create(aSourceName, aDestName, aFileCopyMode, aFileCopyCallback) do begin OnTerminate := ThreadIsReady; FreeOnTerminate := true; Start; // Thread wird gestartet end; end; {------------------------------------------------------------------------------} {- TRuFileCopyThread ----------------------------------------------------------} {------------------------------------------------------------------------------} procedure TRuFileCopyThread.ThreadCopyCallback(ProgressPercent: Integer); begin Synchronize(procedure begin FFileCopyCallback(ProgressPercent); end); end; procedure TRuFileCopyThread.ThreadIsReady(Sender: TObject); begin RuFileCopyThreadResult := FCopyResult; Synchronize(procedure begin FFileCopyCallback(cRuFileCopyReady); end); end; constructor TRuFileCopyThread.Create(aSourceName, aDestName: string; aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback); begin inherited Create(True); // True = Thread nicht automatisch starten FCopyResult := ''; FSourceName := aSourceName; FDestName := aDestName; FFileCopyMode := aFileCopyMode; FFileCopyCallback := aFileCopyCallback; FreeOnTerminate := true; end; procedure TRuFileCopyThread.Execute; begin if not Terminated then FCopyResult := RuFileCopyExecute(FSourceName, FDestName, FFileCopyMode, FFileCopyCallback); end; end. // Hauptprogramm type TForm1 = class(TForm) ... public { Public-Deklarationen } procedure ShowProgress(ProgressPercent: Integer); end; ... procedure TForm1.ShowProgress(ProgressPercent: Integer); begin ProgressBar1.Position := ProgressPercent; if ProgressPercent = cRuFileCopyReady then Label4.Caption := 'FERTIG: '+DateTimeToStr(Now)+' Result['+RuFileCopyThreadResult+']'; end; ToDo: - Exception-Behandlung :roll: - Gibt es eine bessere Lösung für die globale Variable "RuFileCopyThreadResult" und die in der Unit lokalen Variablen StreamCopyBuffer und ProgressPercent [EDIT] Stichwort "ThreadSafe", weil man dann mehrere Dateien gleichzeitig kopieren könnte. Das wär's ;-) |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
@mm1256
Also deine FileCopy-Routine ist echt grausam ... wer hat die denn verbrochen? :shock: Ich wollte die gerade etwas überarbeiten, aber das kommt ja einem neu schreiben gleich.
![]()
Delphi-Quellcode:
Das ist eigentlich alles und du kannst die originale Routine verwenden.
procedure TFoo.FileCopyWorker_DoWork(Sender: TObject; e: TDoWorkEventArgs );
var FileCopyArg : TFileCopyArg; begin FileCopyArg := e.Argument.AsType<TFileCopyArg>; MyCopyFile( FileCopyArg.SourceName, FileCopyArg.DestName, FileCopyArg.FileCopyMode, (Sender as TBackgroundWorker).ReportProgress ); end; procedure TFoo.FileCopyWorker_ProgressChanged( Sender : TObject; e: TProgressChangedEventArgs ); begin ProgressBar1.Position := e.Value; end; procedure TFoo.FileCopyWorker_RunWorkerCompleted( Sender: TObject; e: TRunWorkerCompletedEventArgs ); begin if Assigned( e.Error ) then ShowMessage( e.Error.ToString() ) else if e.Cancelled then ShowMessage( 'Du hast das abgebrochen!' ) else ShowMessage( 'Ich habe fertig!' ); end; PS Das Wichtigste hatte ich vergessen: Du greifst immer wieder auf
Delphi-Quellcode:
zu, und das macht deine Routine mit Progress-Callback extremst langsam. Denn bei jedem Aufruf wandert der Stream an das Ende liest die Position und kehrt zur vorherigen Position zurück!
SourceStream.Size
Lies den Wert am Anfang einmal aus und merk dir diesen, dann läuft auch die Routine mit dem Progress sehr schnell. |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Ich freue mich immer sehr, wenn Verbesserungsvorschläge dazu beitragen, etwas "zu verbessern". Darum habe ich mir deine Vorschläge mal zur Brust genommen: Zitat:
Zitat:
Zitat:
Wenn also Performance raus zu holen ist, dann nicht an dieser Stelle. |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Stell Dir mal vor, dein Programm soll mal im Ausland laufen... Übrigens ist es keine Tugend, alles in eine Routine zu quetschen. Das ist sowas von gestern, ach was sag ich, vorvorvorgestern... Definier doch einfach eine Klasse, führe einfache kleine Methoden ein und schreibe das Ganze, das es verständlich ist. "Clean Code" nennt sich das. Probiere es mal aus. |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Die Reaktionen sind imho leicht übertrieben (bis auf die zur Fehlerbehandlung :wink:). Es gibt deutlich schlimmeren Code. Der Hinweis auf den Workerpool ist für diese Aufgabe zwar legitim, man sollte das imho aber auch mal zu Fuß gemacht haben, damit man eine Vorstellung davon hat, was bei so einer asynchronen Ausführung so passiert. Ein Fehler ist mir ins Auge gefallen: Der RuFileCopyExecute wird in Execute das falsche Callback übergeben. Eigentlich willst du ja die synchronisierte Variante. Das CreateSuspended = true und das Konstrukt mit dem with kannst du dir sparen, wenn du die Eigenschaften, die du im with setzt, direkt im Konstruktor setzt. ![]()
Delphi-Quellcode:
direkt am Ende von Execute auf.
ThreadCopyCallback(cRuFileCopyReady);
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Hallo,
ich habe den Eindruck, dass ihr mit den Exceptions jetzt total am Ziel vorbei argumentiert. Fangen wir doch mal ganz von vorne an. Wozu bzw. in welchen Anwendungsfällen verwende ich überhaupt eine "eigene" Kopier-Routine? Immer dann, wenn die normale WinApi oder was auch immer nicht ausreicht, oder Probleme verursacht, oder die Funktionalität nicht ausreicht. In diesem Fall geht es darum, dass dem TE die Mainform einfriert. Also gibt es zwei grundsätzliche Lösungsmöglichkeiten: Separater Thread und/oder eigene Kopier-Routine. Soweit so gut, nun mal zur praktischen Anwendung. Wenn ich eine oder mehrere Dateien kopiere, dann brauche ich eine Rückmeldung, wenn dabei etwas schief geht, z.B. Festplatte ist voll, Ziel-Datenträger ist schreibgeschützt...was auch immer. Aber eine Exception werfen, weil z.B. die Zieldatei neuer ist als die Quelldatei? Sorry, aber das ist meiner Meinung nach ganz schlechter Programmierstil. Wie will ich in dem Fall eine ich nenne sie mal fatale Exception (z.B. Datenträger ist voll) von einer ganz normalen Meldung "Zieldatei ist neuer" unterscheiden? Hinzu kommt, in manchen Situationen will ich eine Meldung vielleicht gar nicht anzeigen, sondern ganz einfach ältere Dateien durch neuere ersetzen. Wie sieht das dann in eurem Quellcode aus?
Delphi-Quellcode:
Das kann nicht wirklich euer Ernst sein. So würde ich das machen:
try
Dateien kopieren... except // hier den ganzen Rotz auswerten, weil, die ganz normalen User-Messages - wenn // ich sie denn überhaupt dem User anzeigen möchte, landen auch hier. end;
Delphi-Quellcode:
Jeder kann das halten wie er will. Für mich sind Exceptions "Ausnahmen" und wenn ich eine Ausnahme nicht behandeln kann, nach eurer Logik z.B. die Ausnahme "Die Zieldatei ist neuer als die Quelldatei", wozu muss ich sie dann überhaupt erst mal werfen? Das widerspricht dem Grundprinzip der Exceptionbehandlung. Ihr verwechselt da ganz einfach zwei grundverschiedene Situationen: Ein wahlweise nach Bedarf für den User anzuzeigendes Ergebnis eines Kopiervorganges und einer Exception die bei diesem Vorgang aufgetreten ist.
try
Dateien kopieren... Bei Bedarf dem User entsprechende Messages zeigen, oder bei vielen Dateien alle Messages in eine Liste und am Schluss des Kopiervorganges anzeigen except // hier die "echten" Exceptions auswerten, z.B. Datenträger ist voll end; Zitat:
Wenn ich der Meinung bin, dass dem User eine Meldung angezeigt werden muss, dann zeige ich sie ihm. Wenn ich für den Fall, dass der Kopiervorgang ohne Usermeldung ablaufen soll, dem User etwas zeigen will oder muss, dann ist es eine "echte" Exception, z.B. "deine Platte ist voll". Zitat:
Zitat:
|
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
Zitat:
Gruß K-H |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zu Exceptions gibt es hier mehrere Themen und Beiträge und es wurde schon erschöpfend behandelt.
Und ich finde es wesentlich einfacher auf einen speziellen Exception-Typ zu reagieren, als einen String-Rückgabewert zu parsen um herauszufinden, was denn jetzt eigentlich los gewesen ist, und wie ich da am sinnvollsten reagieren könnte. Und wenn dann noch unterschiedliche Sprachen mit ins Spiel kommen, dann brauche ich vor dem Parsen sogar noch G**gle-Translate ... |
AW: CopyFile lässt die Anwendung hängen, wie umgehen?
Zitat:
[OT] Zitat:
[/OT] |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:01 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz