Kopiervorgang mit File.Copy() sehr langsam

Bella_Isy

Erfahrenes Mitglied
Hallo zusammen,

leider habe ich nichts passendes zu meinem Problem gefunden. Mein Problem ist, das mein Kopiervorgang sehr sehr langsam ist und ich mich jetzt frage, ob sich das den nicht beschleunigen lässt.

In meinem Test habe ich 12 GB von einer externen HDD (USB 2.0) auf ein lokales Verzeichnis kopiert. Das ganze hat ca. 12 h gedauert. Wenn ich die selben Daten über Windows kopiert habe, hat das ganze nur ca. 3 h gedauert. Mir ist durch aus bewusst, dass Windows mit C geschrieben ist (glaube ich) und C# um einiges langsamer ist. Aber gibt es nicht irgend einen Trick um das Kopieren in C# zu beschleuigen. Ich denke so in die Richtung, dass man mehrere Kopiervorgänge startet (dürfen sich natürlich nicht beeinträchtigen) oder ist das absoluter Blödsinn.

Hier mal der Weg wie ich das mache. Zuerst ermittel ich den Ziel- & Quellpfad. Anschließend rufe ich die Funktion CopyImages (selbst geschrieben) auf. Ich kopiere die Daten rekursiv. Verzeichnis für Verzeichnis, Datei für Datei. Dabei darf es nicht passieren, dass vorhanden Daten überschrieben werden. Nätürlich Loge ich alles mit und zeige es dem Benutzer in einem Windows Fenster, wo sich auch eine Processbar befindet, die nach jeder Kopie um eins erhöht wird.

Hier ist die Funktion:

Code:
        /// <summary>
        /// Kopiert ein Ordner.
        /// </summary>
        /// <param name="destinationPath">Zielverzeichnis</param>
        /// <param name="sourcePath">Quellverzeichnis</param>
        /// <param name="fRecursive"><c>true:</c>Wenn auch Unterverzeichnisse
        /// mit kopiert werden soll</param>
        private void CopyImages(string destinationPath, string sourcePath,
            bool fRecursive)
        {
            //lokale Variablen
            int i = 0;
            int posSep = 0;
            string sDir = null;
            string[] aDirs = null;
            string sFile = null;
            string[] aFiles = null;

            Application.DoEvents();

            //Zeichen "\" hinzufügen, wenn nicht vorhanden ist
            if (!sourcePath.EndsWith(System.IO.Path.DirectorySeparatorChar.
                ToString()))
            {
                sourcePath += System.IO.Path.DirectorySeparatorChar;
            }

            if (!destinationPath.EndsWith(System.IO.Path.DirectorySeparatorChar.
                ToString()))
            {
                destinationPath += System.IO.Path.DirectorySeparatorChar;
            }

            if (fRecursive)
            {
                //Alle Unterverzeichnis ermitteln
                aDirs = System.IO.Directory.GetDirectories(sourcePath);
                //Jeden Unterordner durchlaufen
                for (i = 0; i <= aDirs.GetUpperBound(0); i++)
                {
                    //ermittelt den letzen '\' im aktuellen Quellverzeichnis
                    posSep = aDirs[i].LastIndexOf(@"\");
                    //Name des Unterordners ermitteln
                    sDir = aDirs[i].Substring((posSep + 1), aDirs[i].Length - 
                        (posSep + 1));
                    //Verzeichnis neu erstellen
                    System.IO.Directory.CreateDirectory(destinationPath + sDir);

                    //Methode erneut aufrufen, um weitere Unterverzeichnis des 
                    //Unterverzeichnis zu kopieren
                    CopyImages((destinationPath + sDir), aDirs[i], fRecursive);
                }
            }

            // Alle Dateien des aktuellen Verzeichnisses
            aFiles = System.IO.Directory.GetFiles(sourcePath);

           
            //Kopiert alle Dateien
            for (i = 0; i <= aFiles.GetUpperBound(0); i++)
            {
                //ermittelt den letzen '\' im aktuellen Quellverzeichnis
                posSep = aFiles[i].LastIndexOf(@"\");

                //Dateinamen ermitteln
                sFile = aFiles[i].Substring((posSep + 1), aFiles[i].Length - 
                    (posSep + 1));

                try
                {
                    //Datei kopieren
                    System.IO.File.Copy(aFiles[i], destinationPath + sFile,
                        false);
                    this.txtLog.Text += Properties.Resources.dgCopyPic01 +
                        sFile + Environment.NewLine + Properties.Resources.
                        dgCopyPic02 + destinationPath + Environment.NewLine;
                    Logfile.WriteLogEntry(Properties.Resources.dgCopyPic01 +
                       sFile + Environment.NewLine + Properties.Resources.
                       dgCopyPic02 + destinationPath);
                   
                }
                catch (Exception ex)
                {
                    //Fehler beim Kopieren
                    //Log - Eintrag
                    Logfile.WriteLogEntry("=> " + ex.Message);
                    this.txtLog.Text += "=> "+ ex.Message + Environment.NewLine;
                }
                finally
                {
                    //Fortschrittsleiste erhöhen
                    prbImprovment.PerformStep();
                    this.Refresh();
                }
            }
        }

Die Nachrichten für den Benutzer werden einer Resource Datei entnommen, weil mein Tool später in mehrere Sprachen übersetzt werden soll. Kommt bitte nicht auf die Idee zu sagen ich soll doch einfach, das manuell machen (also über Windows):), dass geht nicht, weil der Kopiervorgang nur ein Teil eines großeren Firmenspezifischen Wiederherstellungstools ist und dies soll so weit wie möglich vollständig automatisiert werden.

Kann mir ein erfahrender Programmiere vielleicht helfen? Ich habe nämlich noch keinerlei Erfahrung mit performance gerechter Programmierung.

Vielen Dank

Isabelle

PS: Ich programmiere mit C# in .Net. Es handelt sich hier um eine Windowsanwendung
 
Hallo Isabelle,

zunächst solltest du die zugrundeliegende Struktur ändern und die Kopierroutine in einen separaten Thread auslagern. Das Ganze mit GUI-Aktionen zu vermischen und die Verwendung von Application.DoEvents() sind eine wirksame Bremse. Dazu bietet sich die BackgroundWorker-Klasse an. Fortschritts- bzw. Loginfos können per Event (ProgressChanged) an die GUI übermittelt werden. Schaue dir einfach die Klassenreferenz in der MSDN an, die Anendung ist ganz gut dokumentiert.

Pfadkomponenten kannst du übrigends einfach mit System.IO.Path.Combine() miteinander verbinden. Dabei werden evt. fehlende Pfadtrenner (Backslash) automatisch ergänzt.

Gruß
MCoder
 
Hallo MCoder,

vielen Dank für deine Hilfe. Ich werde mal in der MSDN nachlesen. Application.DoEvents() habe ich nur eingefügt, damit der entsprechende Dialog meiner Anwendung sich nicht aufhängt. Ich muss nämlich den Benutzer im Dialog in einem Textfeld jeden Kopierschritt mitteilen. Möchte mein Chef (Welche Datei, wohin kopiert wurde, usw). Geht das alles mit dem Event oder mich ich mir da extra definieren?

Vielen Dank

Isabelle
 
Application.DoEvents() habe ich nur eingefügt, damit der entsprechende Dialog meiner Anwendung sich nicht aufhängt. Ich muss nämlich den Benutzer im Dialog in einem Textfeld jeden Kopierschritt mitteilen.
Wie schon gesagt, so sollte man keinesfalls programmieren. Alle Anzeigen, Ausgaben usw. kannst du an den ProgressChanged-Event des BackgroundWorkers hängen. Damit werden auch die Aufgaben (Kopieren und Dokumentieren) sauber voneinander getrennt. Wahrscheinlich musst du dir noch eine Klasse von "ProgressChangedEventArgs" ableiten, um deine eigenen Information dort unterzubringen.

Gruß
MCoder
 
ich danke dir für deine Antwort. Ich habe mir den entsprechende MSDN Teil schon durchgelesen. Das ist ja garnicht so schwierig wie ich gedacht habe. Mal sehn, ob es dann auch so einwandfrei funktioniert.

Nochmals vielen Dank

Isabelle
 
Hallo zusammen,

ich habe jetzt meine Anwendung so umgeschrieben, dass das Kopieren im Hintergrund ausgeführt wird. Doch nun habe ich das Problem, das nun nach einer Zeit die Ausnahme StackOverflowException, bei schreiben der Log Einträge, ausgelösst wird und ich weiß nicht was ich noch machen soll. Ich muss die Dateien rekursiv kopieren, weil ich nicht weiß wieviele Unterverzeichnisse der jeweilige Ordner hat und ausserdem muss jede Kopie mit protokolliert werden. (Welche Datei wird kopiert? Wohin wird diese kopiert?)

Was kann ich machen. Hier noch mein Code
Code:
        /// <summary>
        /// Kopiert ein Ordner.
        /// </summary>
        /// <param name="destinationPath">Zielverzeichnis</param>
        /// <param name="sourcePath">Quellverzeichnis</param>
        /// <param name="fRecursive"><c>true:</c>Wenn auch Unterverzeichnisse
        /// mit kopiert werden soll</param>
        private void CopyImages(string destinationPath, string sourcePath,
            bool fRecursive)
        {
            //lokale Variablen
            int i = 0;
            int posSep = 0;
            string sDir = null;
            string[] aDirs = null;
            string sFile = null;
            string[] aFiles = null;
            Properties.Settings s = new Properties.Settings();
            int percent = 0;
            string message = string.Empty;
           

            if (fRecursive)
            {
                //Alle Unterverzeichnis ermitteln
                aDirs = System.IO.Directory.GetDirectories(sourcePath);
                //Jeden Unterordner durchlaufen
                for (i = 0; i <= aDirs.GetUpperBound(0); i++)
                {
                    //ermittelt den letzen '\' im aktuellen Quellverzeichnis
                    posSep = aDirs[i].LastIndexOf(@"\");
                    //Name des Unterordners ermitteln
                    sDir = aDirs[i].Substring((posSep + 1), aDirs[i].Length -
                        (posSep + 1));
                    //Verzeichnis neu erstellen
                    Directory.CreateDirectory(Path.Combine(destinationPath,sDir));

                    //Methode erneut aufrufen, um weitere Unterverzeichnis des 
                    //Unterverzeichnis zu kopieren
                    CopyImages(Path.Combine(destinationPath,sDir), aDirs[i], fRecursive);
                }
            }
            // Alle Dateien des aktuellen Verzeichnisses
            aFiles = System.IO.Directory.GetFiles(sourcePath);


            /*Copy all files.*/
            for (i = 0; i <= aFiles.GetUpperBound(0); i++)
            {
                //ermittelt den letzen '\' im aktuellen Quellverzeichnis
                posSep = aFiles[i].LastIndexOf(@"\");

                //Dateinamen ermitteln
                sFile = aFiles[i].Substring((posSep + 1), aFiles[i].Length -
                    (posSep + 1));

                try
                {
                    //Datei kopieren
                    System.IO.File.Copy(aFiles[i], Path.Combine(destinationPath,sFile),
                        false);
                    s.counter+= 1;
                    //Log Eintrag
                    //Prozenzsatz ermitteln
                    percent = s.counter * 100;
                    percent = percent / s.MaxProgress;
                    //Nachricht 
                    message = Properties.Resources.dgCopyPic01 + sFile +
                        Environment.NewLine + Properties.Resources.dgCopyPic02 +
                        destinationPath;
                    backgroundWorker1.ReportProgress(percent, message);
                }
                catch (Exception ex)
                {
                    //Fehler beim Kopieren
                    //Log - Eintrag
                    s.counter += 1;
                    //Prozenzsatz ermitteln
                    percent = s.counter * 100;
                    percent = percent / s.MaxProgress;
                    //Nachricht 
                    message = ex.Message;
                    backgroundWorker1.ReportProgress(percent, message);
                }
            }
        }

        /// <summary>
        /// Tritt ein, wenn die Methode RunWorkerAsync() aufgerufen wird
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            //Dateien kopieren
            this.CopyImages(this.lblPicPath.Text,this.lblImageBackup.Text,true);

        }

         /// <summary>
        /// Tritt ein, wenn der Report geschrieben wird
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_ProgressChanged(object sender, 
            ProgressChangedEventArgs e)
        {
            //Log Eintrag 
            this.txtLog.Text += (string)e.UserState + Environment.NewLine; //<- Hier tritt der Fehler nach einiger Zeit auf
            //Fortschrittsleiste erhöhen
            prbImprovment.PerformStep();
        }

In der MSDN habe ich mir den Eintrag "Problembehandlung bei Ausnahmen: System.StackOverflowException" angeschaut, um das Problem zu lösen.
Da wird geschrieben, das man nicht so viel rekursiv aufrufen soll oder wenn es den sein muss das man die Common Language Runtime entladen soll. Ich weiß nicht was ich machen soll.

Wenn ich die Log nicht schreiben lassen, tritt kein Fehler auf. Die Geschwindingkeit passt auch.

Hat jemand eine Idee, wie ich das ganze mit Logeinträge zum laufen bringe.

Vielen Dank

Isabelle
 
Zuletzt bearbeitet:
Hallo Isabelle,

der Fehler ist ein Indiz dafür, das die Protokollierung ab einer bestimmten Datenmenge länger dauert, als das Kopieren (womit wohl auch schon der Grund für den vorherigen Geschwindigkeitseinbruch gefunden wäre). Versuche mal, statt "+=" die Methode "AppendText" zu verwenden, da diese wesentlich performanter ist.
C#:
this.txtLog.AppendText((string)e.UserState + Environment.NewLine);
Gruß
MCoder
 
Hallo,

ich danke dir. Es hat einwandfrei funktioniert.

Kannst du mir auch den Unterschied zwischen den beiden Methoden erklären, mich interessiert es nämlich auch die Hindergründe zu verstehen von dem was ich mache.

Vielen Dank

Isabelle
 
Hallo,

gibt es den auch eine alternative zu

C#:
//Log-Eintrag anfügen
using (StreamWriter sw = new StreamWriter(path, true))
{
    sw.WriteLine(message);
    sw.WriteLine();
    sw.Close();
}

da habe ich nämlich genau das gleiche Problem

Gruß Isabelle
 
Zuletzt bearbeitet von einem Moderator:
Hi.
Hallo,

gibt es den auch eine alternative zu

C#:
//Log-Eintrag anfügen
using (StreamWriter sw = new StreamWriter(path, true))
{
    sw.WriteLine(message);
    sw.WriteLine();
    sw.Close();
}

da habe ich nämlich genau das gleiche Problem
Heißt das du öffnest und schließt jedesmal wenn du eine Nachricht in die Logdatei schreiben willst die Datei?

Öffne die Datei am Anfang vom Programm und schließe sie erst wenn du fertig bist.

Gruß
 
Zuletzt bearbeitet von einem Moderator:

Neue Beiträge

Zurück