c# file-io progress-bar backgroundworker file-copying

c# - Copia de archivo con barra de progreso



file-io progress-bar (6)

Aquí hay una solución optimizada que utiliza extensiones .NET y un búfer doble para un mejor rendimiento. Se agrega una nueva sobrecarga de CopyTo a FileInfo con una Acción que indica progreso solo cuando ha cambiado.

Esta implementación de muestra en WPF con una barra de progreso llamada progressBar1 que realiza la operación de copia en segundo plano.

private FileInfo _source = new FileInfo(@"C:/file.bin"); private FileInfo _destination = new FileInfo(@"C:/file2.bin"); private void CopyFile() { if(_destination.Exists) _destination.Delete(); Task.Run(()=>{ _source.CopyTo(_destination, x=>Dispatcher.Invoke(()=>progressBar1.Value = x)); }).GetAwaiter().OnCompleted(() => MessageBox.Show("File Copied!")); }

Aquí hay un ejemplo para una aplicación de consola

class Program { static void Main(string[] args) { var _source = new FileInfo(@"C:/Temp/bigfile.rar"); var _destination = new FileInfo(@"C:/Temp/bigfile2.rar"); if (_destination.Exists) _destination.Delete(); _source.CopyTo(_destination, x => Console.WriteLine($"{x}% Complete")); Console.WriteLine("File Copied."); } }

Para usar, cree un archivo nuevo, como FileInfoExtensions.cs y agregue este código:

public static class FileInfoExtensions { public static void CopyTo(this FileInfo file, FileInfo destination, Action<int> progressCallback) { const int bufferSize = 1024 * 1024; //1MB byte[] buffer = new byte[bufferSize], buffer2 = new byte[bufferSize]; bool swap = false; int progress = 0, reportedProgress = 0, read = 0; long len = file.Length; float flen = len; Task writer = null; using (var source = file.OpenRead()) using (var dest = destination.OpenWrite()) { dest.SetLength(source.Length); for (long size = 0; size < len; size += read) { if ((progress = ((int)((size / flen) * 100))) != reportedProgress) progressCallback(reportedProgress = progress); read = source.Read(swap ? buffer : buffer2, 0, bufferSize); writer?.Wait(); // if < .NET4 // if (writer != null) writer.Wait(); writer = dest.WriteAsync(swap ? buffer : buffer2, 0, read); swap = !swap; } writer?.Wait(); //Fixed - Thanks @sam-hocevar } } }

El doble buffer funciona usando un hilo para leer y un hilo para escribir, por lo que la velocidad máxima está dictada solo por el más lento de los dos. Se utilizan dos búferes (un búfer doble), lo que garantiza que los subprocesos de lectura y escritura nunca utilicen el mismo búfer al mismo tiempo.

Ejemplo: el código se lee en el búfer 1, luego cuando finaliza la lectura, una operación de escritura comienza a escribir el contenido del búfer 1. Sin esperar a terminar de escribir, el búfer se intercambia al búfer 2 y los datos se leen al búfer 2 mientras el búfer 1 está todavía siendo escrito. Una vez que la lectura se completa en el búfer 2, espera que se complete la escritura en el búfer 1, comienza a escribir búfer 2 y el proceso se repite. Esencialmente, 1 hilo siempre está leyendo, y uno siempre está escribiendo.

WriteAsync utiliza E / S superpuestas , que utiliza puertos de E / S de finalización , que dependen del hardware para realizar operaciones asincrónicas en lugar de hilos, lo que lo hace muy eficiente. TLDR: mentí sobre que hay 2 hilos, pero el concepto es el mismo.

Usé este código:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; using System.IO; namespace WindowsApplication1 { public partial class Form1 : Form { // Class to report progress private class UIProgress { public UIProgress(string name_, long bytes_, long maxbytes_) { name = name_; bytes = bytes_; maxbytes = maxbytes_; } public string name; public long bytes; public long maxbytes; } // Class to report exception { private class UIError { public UIError(Exception ex, string path_) { msg = ex.Message; path = path_; result = DialogResult.Cancel; } public string msg; public string path; public DialogResult result; } private BackgroundWorker mCopier; private delegate void ProgressChanged(UIProgress info); private delegate void CopyError(UIError err); private ProgressChanged OnChange; private CopyError OnError; public Form1() { InitializeComponent(); mCopier = new BackgroundWorker(); mCopier.DoWork += Copier_DoWork; mCopier.RunWorkerCompleted += Copier_RunWorkerCompleted; mCopier.WorkerSupportsCancellation = true; OnChange += Copier_ProgressChanged; OnError += Copier_Error; button1.Click += button1_Click; ChangeUI(false); } private void Copier_DoWork(object sender, DoWorkEventArgs e) { // Create list of files to copy string[] theExtensions = { "*.jpg", "*.jpeg", "*.bmp", "*.png", "*.gif" }; List<FileInfo> files = new List<FileInfo>(); string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); DirectoryInfo dir = new DirectoryInfo(path); long maxbytes = 0; foreach (string ext in theExtensions) { FileInfo[] folder = dir.GetFiles(ext, SearchOption.AllDirectories); foreach (FileInfo file in folder) { if ((file.Attributes & FileAttributes.Directory) != 0) continue; files.Add(file); maxbytes += file.Length; } } // Copy files long bytes = 0; foreach (FileInfo file in files) { try { this.BeginInvoke(OnChange, new object[] { new UIProgress(file.Name, bytes, maxbytes) }); File.Copy(file.FullName, @"c:/temp/" + file.Name, true); } catch (Exception ex) { UIError err = new UIError(ex, file.FullName); this.Invoke(OnError, new object[] { err }); if (err.result == DialogResult.Cancel) break; } bytes += file.Length; } } private void Copier_ProgressChanged(UIProgress info) { // Update progress progressBar1.Value = (int)(100.0 * info.bytes / info.maxbytes); label1.Text = "Copying " + info.name; } private void Copier_Error(UIError err) { // Error handler string msg = string.Format("Error copying file {0}/n{1}/nClick OK to continue copying files", err.path, err.msg); err.result = MessageBox.Show(msg, "Copy error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); } private void Copier_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // Operation completed, update UI ChangeUI(false); } private void ChangeUI(bool docopy) { label1.Visible = docopy; progressBar1.Visible = docopy; button1.Text = docopy ? "Cancel" : "Copy"; label1.Text = "Starting copy..."; progressBar1.Value = 0; } private void button1_Click(object sender, EventArgs e) { bool docopy = button1.Text == "Copy"; ChangeUI(docopy); if (docopy) mCopier.RunWorkerAsync(); else mCopier.CancelAsync(); } } }

publicado here (el que nobugz publicó) al copiar archivos y mostrar el estado en la barra de progreso.

Quería aumentar continuamente el valor de la barra de progreso durante la copia, especialmente los archivos de gran tamaño. Lo que sucede en este código de ejemplo es que el valor en la barra de progreso se detiene en cada archivo copiado y luego de que un archivo se haya copiado aumentará al tamaño del siguiente archivo que se va a copiar. Quería que funcionara como CopyFileEx en Windows, que la barra de progreso aumenta continuamente al copiar (no puedo usar CopyFileEx porque quería tener mi propia implementación).


Hacer su propia lógica de copia de archivos mediante el uso de 2 flujos como lo presenta Gal es una opción viable, pero no se recomienda únicamente porque hay una operación de Windows profundamente integrada que está optimizada en confiabilidad, seguridad y rendimiento llamada CopyFileEx.

Dicho esto, en el siguiente artículo: http://msdn.microsoft.com/en-us/magazine/cc163851.aspx hacen exactamente lo que quieres, pero por supuesto debes usar CopyFileEx

Buena suerte

** EDIT ** (solucionado mi respuesta, mal escrito)


Me gusta esta solución, porque

El motor de copia está en el marco

public delegate void IntDelegate(int Int); public static event IntDelegate FileCopyProgress; public static void CopyFileWithProgress(string source, string destination) { var webClient = new WebClient(); webClient.DownloadProgressChanged += DownloadProgress; webClient.DownloadFileAsync(new Uri(source), destination); } private static void DownloadProgress(object sender, DownloadProgressChangedEventArgs e) { if(FileCopyProgress != null) FileCopyProgress(e.ProgressPercentage); }

Rutas UNC

Esto debería funcionar con rutas UNC siempre que los permisos estén configurados. Si no, obtendrá este error, en cuyo caso, yo voto por la ruta del usuario de la solicitud autenticada.

System.UnauthorizedAccessException : se System.UnauthorizedAccessException acceso a la ruta ''/ testws01 / c $ / foo''.

ASP.NET no está autorizado para acceder al recurso solicitado. Considere otorgar derechos de acceso al recurso a la identidad de la solicitud ASP.NET . ASP.NET tiene una identidad de proceso base (típicamente {MACHINE} / ASPNET en IIS 5 o Servicio de red en IIS 6 e IIS 7, y la identidad configurada del grupo de aplicaciones en IIS 7.5) que se utiliza si la aplicación no se hace pasar por otra. Si la aplicación imita a través de <identity impersonate="true"/> , la identidad será el usuario anónimo (generalmente IUSR_MACHINENAME) o el usuario de la solicitud autenticada.


Necesitas algo como esto:

public delegate void ProgressChangeDelegate(double Persentage, ref bool Cancel); public delegate void Completedelegate(); class CustomFileCopier { public CustomFileCopier(string Source, string Dest) { this.SourceFilePath = Source; this.DestFilePath = Dest; OnProgressChanged += delegate { }; OnComplete += delegate { }; } public void Copy() { byte[] buffer = new byte[1024 * 1024]; // 1MB buffer bool cancelFlag = false; using (FileStream source = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read)) { long fileLength = source.Length; using (FileStream dest = new FileStream(DestFilePath, FileMode.CreateNew, FileAccess.Write)) { long totalBytes = 0; int currentBlockSize = 0; while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0) { totalBytes += currentBlockSize; double persentage = (double)totalBytes * 100.0 / fileLength; dest.Write(buffer, 0, currentBlockSize); cancelFlag = false; OnProgressChanged(persentage, ref cancelFlag); if (cancelFlag == true) { // Delete dest file here break; } } } } OnComplete(); } public string SourceFilePath { get; set; } public string DestFilePath { get; set; } public event ProgressChangeDelegate OnProgressChanged; public event Completedelegate OnComplete; }

Simplemente ejecútelo en hilo separado y sunscribe para el evento OnProgressChanged


Puede copiar partes de la secuencia de archivos de cada archivo y actualizar después de cada "porción" que actualice. Por lo tanto, será más continuo: también puede calcular fácilmente el tamaño relativo del "fragmento" actual que está copiando en relación con el tamaño total del flujo para mostrar el porcentaje correcto realizado.


puede usar Dispatcher para actualizar su ProgressBar.

UpdateProgressBarDelegate updatePbDelegate = new UpdateProgressBarDelegate(ProgressBar1.SetValue); Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { ProgressBar.ValueProperty, value });