File Copy with Progress Bar

后端 未结 6 688
有刺的猬
有刺的猬 2020-11-27 02:58

I used this code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;

namespace Windo         


        
6条回答
  •  执笔经年
    2020-11-27 03:38

    Here's an optimized solution that utilizes .NET extensions and a double-buffer for better performance. A new overload of CopyTo is added to FileInfo with an Action that indicates progress only when it has changed.

    This sample implementation in WPF with a progress bar named progressBar1 that performs the copy operation in the background.

    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:

    public static class FileInfoExtensions
    {
      public static void CopyTo(this FileInfo file, FileInfo destination, Action 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. Once the read completes in buffer 2, it waits for write to complete on buffer 1, starts writing buffer 2, and the process repeats. Essentially, 1 thread is always reading, and one is always writing.

    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. TLDR: I lied about there being 2 threads, but the concept is the same.

提交回复
热议问题