Ordner schnell kopieren

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von Popa21.

    Ordner schnell kopieren

    Hi,in meinen Programm wird unter anderem ein sehr großer Ordner(2-3GB) kopiert, jetzt ist mir aufgefallen, dass das ganze sehr viel langsamer ist als im Windows Explorer.
    Also wollte ich mal fragen, ob ihr Ideen habt, wie es schneller geht.Hier mein Code:

    VB.NET-Quellcode

    1. Private Sub copyDirectory(source As String, target As String)
    2. If Not Directory.Exists(target) Then Directory.CreateDirectory(target)
    3. For Each file In Directory.GetFiles(source)
    4. IO.File.Copy(file, Path.Combine(target, Path.GetFileName(file)))
    5. Me.Invoke(New doAction(AddressOf updateProgress))
    6. Application.DoEvents()
    7. Next
    8. For Each folder In Directory.GetDirectories(source)
    9. copyDirectory(folder, Path.Combine(target, folder.Split("\")(UBound(folder.Split("\")))))
    10. Me.Invoke(New doAction(AddressOf updateProgress))
    11. Application.DoEvents()
    12. Next
    13. End Sub

    LG Popa21
    Bei den Maßen der Einfachheit stößt man an Grenzen, aber: "Es geht immer komplizierter".
    Kein Application.DoEvents verwenden, das hat hier nichts verloren(das hat so gut wie nirgends etwas verloren).
    Verwende BeginInvoke anstatt Invoke, damit der Thread nicht auf den UiThread wartet...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Invoken kostet immer viel Zeit. Ist updateProgress dazu da eine ProgressBar zu aktualisieren? Falls ja, lagere das aus der Schleife aus und mach das separat.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Vom gezeigten Code her ist völlig unbestimmt, ob hier ühaupt mit Threading gearbeitet wird.
    Das Invoken deutet auf Nebenläufigkeit hin, Application.DoEvents() spricht dagegen - letzteres ergibt ja nur Sinn, wenn es nicht in Nebenläufigkeit erfolgt.

    Weiters ist zu bedenken, dass Threading den Vorgang auch nicht beschleunigt oder verlangsamt.
    Allenfalls das Invoke, ja, dassis schlecht gemacht und verlangsamt bischen, aber obs mehr als zwei Prozent ausmacht, bezweifel ich.
    Weil wird ja pro Datei und Ordner nur einmal aufgerufen.
    (Ausser in updateGui passieren schreckliche Dinge, dann ist das natürlich die Bremse - ist hier aber nicht zu sehen)
    Danke an alle, ich probiere das alles heute noch aus.
    @jvbsl Mach ich
    @mrMo Probiere ich aus, soll ich es in einen Timer packen?
    @ErfinderDesRades Das ist Threading, es läuft in einen BackgroundWorker. Das ist aber nur dazu gedacht, dass das Form nicht einfriert.

    Ich werde die DoEvents wegmachen, die Aktualisierung der ProgressBar in einen Timer packen.

    LG Popa21

    Edit:
    Habs jetzt eingetragen, die Geschwindigkeit ist super, aber da ist das nächste Problem: das Programm lägt sehr arg beim kopieren.
    Bei den Maßen der Einfachheit stößt man an Grenzen, aber: "Es geht immer komplizierter".

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Popa21“ ()

    @ErfinderDesRades
    Ich habe schon getüftelt, es laggt zwar nicht mehr, aber bei Geschätzten 200MB an kopierten Daten zeigt mein Programm mir 0KB/s an und mit den Explorer bemerke ich auch, dass nichts mehr erstellt wird. Hier mein Code:

    Variablen:

    VB.NET-Quellcode

    1. Private oldKBytes As Long = 0
    2. Private copyerThread As New Threading.Thread(New Threading.ThreadStart(AddressOf copyerSub))
    3. Private tickerProgressThread As Threading.Thread
    4. Private tickerSpeedThread As Threading.Thread


    Beim Starten:

    VB.NET-Quellcode

    1. copyerThread.IsBackground = True


    Beim Schließen:

    VB.NET-Quellcode

    1. Try
    2. copyerThread.Abort()
    3. If Not IsNothing(tickerProgressThread) Then tickerProgressThread.Abort()
    4. If Not IsNothing(tickerSpeedThread) Then tickerSpeedThread.Abort()
    5. Catch ex As Exception
    6. End Try


    Starten des kopierens:

    VB.NET-Quellcode

    1. Me.copyerThread.Start()

    Subs:

    VB.NET-Quellcode

    1. Private Sub copyerSub()
    2. Try
    3. Me.Invoke(Sub() tickerProgress.Enabled = True)
    4. Me.Invoke(Sub() tickerSpeed.Enabled = True)
    5. copyDirectory(selectedSource.Text, Path.Combine(selectedTarget.Text, selectedSource.Text.Split("\")(UBound(selectedSource.Text.Split("\")))))
    6. Me.Invoke(Sub() Me.statusBar.Maximum = 0)
    7. Me.Invoke(Sub() Me.statusBar.Value = 0)
    8. Me.Invoke(Sub() Me.actualSpeed.Text = "")
    9. ...
    10. Catch uaex As UnauthorizedAccessException
    11. showError("Fehler beim kopieren: Zugriff verweigert")
    12. Me.Invoke(New doAction(AddressOf Me.copyerThread.Abort))
    13. Catch ioex As IOException
    14. showError("Fehler: Auf eines der Verzeichnise oder Dateien wird bereits zugegriffen")
    15. Catch abex As Threading.ThreadAbortException
    16. Catch ex As Exception
    17. showError("Ein Fehler ist in xxx aufgetreten: " & ex.ToString)
    18. Me.Invoke(New doAction(AddressOf Me.copyerThread.Abort))
    19. End Try
    20. End Sub
    21. Private Sub updateProgress(sender As Object, e As EventArgs) Handles tickerProgress.Tick
    22. tickerProgressThread = New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf updateProgressTask))
    23. tickerProgressThread.Start(Path.Combine(selectedTarget.Text, selectedSource.Text.Split("\")(UBound(selectedSource.Text.Split("\")))))
    24. End Sub
    25. Private Sub updateProgressTask(arg As Object)
    26. Try
    27. Dim kbytes As Long = 0
    28. For Each info In New DirectoryInfo(CStr(arg)).GetFiles("*", SearchOption.AllDirectories)
    29. kbytes += info.Length
    30. Next
    31. kbytes = kbytes / 1024
    32. Me.BeginInvoke(Sub() statusBar.Value = kbytes)
    33. Catch ex As Exception
    34. End Try
    35. End Sub
    36. Private Sub updateSpeed(sender As Object, e As EventArgs) Handles tickerSpeed.Tick
    37. tickerProgressThread = New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf updateSpeedTask))
    38. tickerProgressThread.Start(Path.Combine(selectedTarget.Text, selectedSource.Text.Split("\")(UBound(selectedSource.Text.Split("\")))))
    39. End Sub
    40. Private Sub updateSpeedTask(arg As Object)
    41. Try
    42. Dim kbytes As Long = 0
    43. For Each info In New DirectoryInfo(CStr(arg)).GetFiles("*", SearchOption.AllDirectories)
    44. kbytes += info.Length
    45. Next
    46. kbytes = kbytes / 1024
    47. Dim speedKBytes As Long = (kbytes - oldKBytes) * 4
    48. Dim speedText As String = ""
    49. If speedKBytes / 1024 >= 1 Then
    50. speedKBytes = speedKBytes / 1024
    51. speedText = speedKBytes & " MB/s"
    52. If speedKBytes / 1024 >= 1 Then
    53. speedKBytes = speedKBytes / 1024
    54. speedText = speedKBytes & " GB/s"
    55. End If
    56. Else
    57. speedText = speedKBytes & " KB/s"
    58. End If
    59. Me.BeginInvoke(Sub() actualSpeed.Text = speedText)
    60. oldKBytes = kbytes
    61. Catch ex As Exception
    62. End Try
    63. End Sub
    64. Private Sub copyDirectory(source As String, target As String)
    65. If Not Directory.Exists(target) Then Directory.CreateDirectory(target)
    66. For Each file In Directory.GetFiles(source)
    67. Dim array_length As Integer = CInt(Math.Pow(2, 21))
    68. Dim dataArray As Byte() = New Byte(array_length - 1) {}
    69. Using fsread As New FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None, array_length)
    70. Using bwread As New BinaryReader(fsread)
    71. Using fswrite As New FileStream(Path.Combine(target, Path.GetFileName(file)), FileMode.Create, FileAccess.Write, FileShare.None, array_length)
    72. Using bwwrite As New BinaryWriter(fswrite)
    73. While True
    74. Dim read As Integer = bwread.Read(dataArray, 0, array_length)
    75. If 0 = read Then
    76. Exit While
    77. End If
    78. bwwrite.Write(dataArray, 0, read)
    79. End While
    80. fsread.Close()
    81. fswrite.Close()
    82. End Using
    83. End Using
    84. End Using
    85. End Using
    86. Next
    87. For Each folder In Directory.GetDirectories(source)
    88. copyDirectory(folder, Path.Combine(target, folder.Split("\")(UBound(folder.Split("\")))))
    89. Next
    90. End Sub


    Beim ... ist noch Code drinnen, der nichts mit den Fehler zu tuen hat
    Edit:
    Bild angehängt
    Bilder
    • erro1.jpg

      4,63 kB, 305×51, 180 mal angesehen
    Bei den Maßen der Einfachheit stößt man an Grenzen, aber: "Es geht immer komplizierter".

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Popa21“ ()

    Me.Invoke(New doAction(AddressOf Me.copyerThread.Abort))

    Das kannst mal überall entfernen, ein Thread kann sich(zumindest Native threads) nicht selbst abbrechen, außerdem ist es sowieso besser ohne, da er da ja dann sauber beendet wird...

    Dann ist die Art, wie du messen willst auch nicht wirklich SInnvoll, da für die größe auch jedesmal durch alle subfiles/folder iteriert werden muss, was länger dauert, als das updaten der werte, weshalb das niemals auch nur annähernd genau werden kann.

    Du kannst von mir aus die größe der zu kopierenden Daten in einem Hintergrundthread machen, ich würde aber trotzdem schon parallel kopieren.

    Ansonsten hast du maximal 2 Threads und nicht 3, denn das messen der Kopiergeschwindigkeit machst du in der copyDirectory methode direkt. Da hast du ja die größe die du jeweils kopierst, musst nur noch die Zeit messen..
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    @Popa21 In solchen Fällen sag ich immer:
    Der Algorithmus muss zunächst in einer Button_Click funktionieren, da kannst Du ihn entwickeln und optimieren.
    Wenn dann alles läuft, kannst Du ihn in einen Thread packen, um die GUI frei zu haben.
    Allerdings wird er in einem Thread nicht schneller, es sei denn, Du kopierst Daten von Festplatte X1 nach Festplatte X2 sowie parallel dazu von Festplatte X3 nach Festplatte X4.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ok, ich nutze jetzt die Klasse von @~blaze~: Asynchrones Kopieren von Dateien und Ordnern, und in copyDirectory:

    VB.NET-Quellcode

    1. Dim copyHelper As New CopyHelper(Math.Pow(2, 19))
    2. copyProgress = copyHelper.CopyDirectory(source, target, "*", SearchOption.AllDirectories)
    3. AddHandler copyProgress.Finished, AddressOf copyFinished
    4. While Not copyProgress.Canceled
    5. End While


    Der Rest:

    VB.NET-Quellcode

    1. Private copyProgress As CopyProgress
    2. Private Sub copyFinished(sender As Object, e As EventArgs)
    3. copyProgress.Cancel()
    4. End Sub
    5. Private Sub Main_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    6. Try
    7. ...
    8. copyProgress.Cancel()
    9. ...
    10. Catch ex As Exception
    11. End Try
    12. End Sub
    Bei den Maßen der Einfachheit stößt man an Grenzen, aber: "Es geht immer komplizierter".

    xChRoNiKx schrieb:

    Dann sei doch so nett und poste deinen Code

    @Popa21 ISt das Forum nicht würdig, von Dir mit Deinem Copy-Directory-Problem auch ein Stück Code zu sehen, wo solch passiert?
    Die Klasse CopyHelper kann ich weder bei Dir noch bei @~blaze~ finden.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Neues Problem

    Hi, sorry wegen des Doppelposts, aber ich stehe vor einen sehr komischen Problem ?( :
    Wegen Schwierigkeiten habe ich statt @~blaze~'s Klasse meinen eigenen Code geschrieben, und ich habe die Geschwindigkeits-Anzeige weggemacht und dafür ein Fortschritts-Anzeige gemacht. Alles geht super, aber mir ist etwas aufgefallen: Der zu kopierende Ordner ist 1,2GB groß, aber der Endordner war schon bei der Hälfte des Vorgangs 5,6GB groß :S .

    Hier mein Code:

    VB.NET-Quellcode

    1. Private Sub copyDirectory(source As String, target As String)
    2. Try
    3. Dim files As New List(Of String)
    4. Me.Invoke(Sub() Me.actualProgress.Text = "Erstelle Verzeichniss-Struktur")
    5. For Each folder As String In Directory.GetDirectories(source, "*", SearchOption.AllDirectories)
    6. Directory.CreateDirectory(target & folder.Replace(source, ""), New DirectoryInfo(folder).GetAccessControl)
    7. Next
    8. Me.Invoke(Sub() Me.actualProgress.Text = "Ermittle zu verschiebede Dateien")
    9. For Each file As String In Directory.GetFiles(source, "*", SearchOption.AllDirectories)
    10. files.Add(file.Replace(source, ""))
    11. Next
    12. Me.Invoke(Sub() Me.statusBar.Maximum = files.Count)
    13. For Each file As String In files
    14. copyFile(source & file, target & file)
    15. Me.Invoke(Sub() Me.statusBar.Increment(1))
    16. Me.Invoke(Sub() Me.actualProgress.Text = Me.statusBar.Value / (Me.statusBar.Maximum / 100))
    17. Next
    18. Catch ex As Exception
    19. showError("Ein Fehler ist in FileMover .NET aufgetreten: " & ex.ToString)
    20. End Try
    21. End Sub
    22. Private Sub copyFile(sourceFile As String, targetFile As String)
    23. Dim cache(Math.Pow(2, 18)) As Byte
    24. Dim sourceStream As FileStream = New FileInfo(sourceFile).OpenRead
    25. Dim targetStream As FileStream = File.Create(targetFile, Math.Pow(2, 18), FileOptions.None, New FileInfo(sourceFile).GetAccessControl())
    26. While True
    27. If sourceStream.Read(cache, 0, Math.Pow(2, 18)) = 0 Then
    28. Exit While
    29. End If
    30. targetStream.Write(cache, 0, Math.Pow(2, 18))
    31. End While
    32. sourceStream.Close()
    33. targetStream.Close()
    34. sourceStream.Dispose()
    35. targetStream.Dispose()
    36. End Sub


    Hat einer eine Erklärung für das?

    LG Popa21
    Bei den Maßen der Einfachheit stößt man an Grenzen, aber: "Es geht immer komplizierter".

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Popa21“ ()

    Hi
    da sind mehrere Fehler drin:
    Du schreibst mehr Daten, als im Puffer drin:

    VB.NET-Quellcode

    1. Dim c As Integer
    2. Do
    3. c = sourceStream.Read(cache, 0, cache.Length)
    4. targetStream.Write(cache, 0, c)
    5. Loop While c > 0

    Außerdem ist bei dir Option Strict auf Off, mach' das rein. 2^n lässt sich als 1 << n schreiben. Wenn du ein Array mit der Größe einer 2-er-Potenz haben willst, musst du noch eins abziehen: Dim x((1 << n) - 1) As Byte

    Für dich außerdem interessant sollte die System.IO.Path-Klasse sein, die dir die String.Replace-Aufrufe ersetzt.

    Viele Grüße
    ~blaze~
    @ErfinderDesRades Danke
    @~blaze~ Ups, werds fixen. Das Math.Pow lasse ich, damit es leserlich bleibt. Ohne den String.Replace geht der Code nicht, den wenn zb. er den Ordner C:/blub kopiert und er gerade die Datei C:/blub/bla/bla.txt gefunden hat, speichert er "/bla/bla.txt". Das kann man einfach an den Pfad vom Zielordner anhängen.
    Teste das alles noch heute.

    Edit1:
    Kenne mich in IO.Path eh aus
    Bei den Maßen der Einfachheit stößt man an Grenzen, aber: "Es geht immer komplizierter".