简体   繁体   English

使用进度条进行文件复制

[英]File Copy with Progress Bar

I used this code: 我用过这段代码:

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();
    }
  }
}

posted here (the one that nobugz posted) in copying files and displaying the status in progress bar. 发布在这里nobugz发布的那个)复制文件并在进度条中显示状态。

I wanted to continuously increment the value of the progress bar while copying, especially large files. 我希望在复制时不断增加进度条的值,尤其是大文件。 What happens in this sample code is that the value in progress bar stops on every file copied and after one file has been copied it will then increment to the size of the next file to be copied. 此示例代码中发生的情况是进度条中的值在复制的每个文件上停止,并且在复制一个文件之后,它将增加到要复制的下一个文件的大小。 I wanted it to work like CopyFileEx in Windows that progress bar continuously increment when copying (I cant use CopyFileEx because I wanted to have my own implementation). 我希望它像Windows中的CopyFileEx一样工作,复制时进度条不断增加(我不能使用CopyFileEx因为我想拥有自己的实现)。

You need something like this: 你需要这样的东西:

    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;
    }

Just run it in separate thread and subscribe for OnProgressChanged event. 只需在单独的线程中运行它并订阅OnProgressChanged事件。

I like this solution, because 我喜欢这个解决方案,因为

The copy engine is in the framework 复制引擎在框架中

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);
}

UNC Paths UNC路径

This should work with UNC paths as long as the permissions are set up. 只要设置了权限,这应该适用于UNC路径。 If not, you will get this error, in which case, I vote for the authenticated request user route. 如果没有,您将收到此错误,在这种情况下,我投票支持经过身份验证的请求用户路由。

System.UnauthorizedAccessException : Access to the path '\\testws01\\c$\\foo' is denied. System.UnauthorizedAccessException :拒绝访问路径'\\ testws01 \\ c $ \\ foo'。

ASP.NET is not authorized to access the requested resource. ASP.NET无权访问所请求的资源。 Consider granting access rights to the resource to the ASP.NET request identity . 考虑将资源的访问权限授予ASP.NET请求标识 ASP.NET has a base process identity (typically {MACHINE}\\ASPNET on IIS 5 or Network Service on IIS 6 and IIS 7, and the configured application pool identity on IIS 7.5) that is used if the application is not impersonating. ASP.NET具有基本进程标识(IIS 5上通常为{MACHINE} \\ ASPNET,IIS 6和IIS 7上为网络服务,IIS 7.5上已配置的应用程序池标识),如果应用程序未模拟,则使用该标识。 If the application is impersonating via <identity impersonate="true"/> , the identity will be the anonymous user (typically IUSR_MACHINENAME) or the authenticated request user. 如果应用程序通过<identity impersonate="true"/> ,则标识将是匿名用户(通常为IUSR_MACHINENAME)或经过身份验证的请求用户。

Making your own file copy logic by using 2 streams as presented by Gal is a viable option but its not recommended solely because there is a deeply intergrated windows operation which is optimized in reliability, security and performance named CopyFileEx. 通过使用Gal提供的2个流来制作自己的文件复制逻辑是一个可行的选择,但不建议仅仅因为有一个深度集成的Windows操作,它在可靠性,安全性和性能方面进行了优化,名为CopyFileEx。

that said, in the following article: http://msdn.microsoft.com/en-us/magazine/cc163851.aspx they do exactly what you want, but ofcourse you have to use CopyFileEx 在下面的文章中说: http//msdn.microsoft.com/en-us/magazine/cc163851.aspx他们完全按照你的意愿行事,但当然你必须使用CopyFileEx

Good luck 祝好运

** EDIT ** (fixed my answer, badly witten) **编辑**(修复我的答案,严重失误)

Here's an optimized solution that utilizes .NET extensions and a double-buffer for better performance. 这是一个优化的解决方案,它利用.NET扩展和双缓冲来提高性能。 A new overload of CopyTo is added to FileInfo with an Action that indicates progress only when it has changed. CopyTo的一个新重载被添加到FileInfo,其中一个Action仅在更改时指示进度。

This sample implementation in WPF with a progress bar named progressBar1 that performs the copy operation in the background. WPF中的此示例实现,其中包含名为progressBar1的进度条,该进度条在后台执行复制操作。

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!"));
}

Here's an example for a Console Application 这是控制台应用程序的示例

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.");
  }
}

To use, create a new file, such as FileInfoExtensions.cs and add this code: 要使用,请创建一个新文件,例如FileInfoExtensions.cs并添加以下代码:

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
    }
  }
}

The double buffer works by using one thread to read and one thread to write, so the max speed is dictated only by the slower of the two. 双缓冲区通过使用一个线程来读取和一个线程来写入,因此最大速度仅由两者中较慢的线程决定。 Two buffers are used (a double buffer), ensuring that the read and write threads are never using the same buffer at the same time. 使用两个缓冲区(双缓冲区),确保读写线程永远不会同时使用相同的缓冲区。

Example: the code reads into buffer 1, then when the read completes, a write operation starts writing the contents of buffer 1. Without waiting finish writing, the buffer is swapped to buffer 2 and data is read into buffer 2 while buffer 1 is still being written. 示例:代码读入缓冲区1,然后当读取完成时,写操作开始写入缓冲区1的内容。无需等待完成写入,缓冲区交换到缓冲区2,数据被读入缓冲区2,而缓冲区1仍然正在写 Once the read completes in buffer 2, it waits for write to complete on buffer 1, starts writing buffer 2, and the process repeats. 一旦读取在缓冲区2中完成,它等待缓冲区1上的写入完成,开始写入缓冲区2,并重复该过程。 Essentially, 1 thread is always reading, and one is always writing. 基本上,1个线程总是在读取,而一个总是在写入。

WriteAsync uses overlapped I/O , which utilizes I/O completion ports , which rely on hardware to perform asynchronous operations rather than threads, making this very efficient. WriteAsync使用重叠I / O ,它利用I / O完成端口 ,它依赖于硬件来执行异步操作而不是线程,这使得它非常有效。 TLDR: I lied about there being 2 threads, but the concept is the same. TLDR:我谎称有2个线程,但概念是一样的。

You can copy parts of the file stream from each file, and update after each "chunk" you update. 您可以从每个文件复制部分文件流,并在更新的每个“块”之后更新。 Thus it will be more continuous - you can also easily calculate the relative size of the current "chunk" you are copying relative to the total stream size in order to show the correct percentage done. 因此它将更加连续 - 您还可以轻松计算相对于总流大小复制的当前“块”的相对大小,以显示正确的完成百分比。

you can use Dispatcher to update your ProgressBar . 您可以使用Dispatcher更新ProgressBar。

UpdateProgressBarDelegate updatePbDelegate = new UpdateProgressBarDelegate(ProgressBar1.SetValue);

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM