复制多个文件并报告进度C#

时间:2017-01-19 19:28:55

标签: c#

在查看多个问题/答案后,我无法找到解决问题的方法。我记得我从StackOverflow的一些问题中得到了这个代码,它完美地运行但仅适用于一个文件。我想要的是多个文件。

这是原始的CopyTo功能:

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

以下是我开始文件复制过程的方法:

                var ficheiro = ficheirosCopia.ElementAt(x);
                var _source = new FileInfo(ficheiro.Key);
                var _destination = new FileInfo(ficheiro.Value);

                if (_destination.Exists)
                {
                    _destination.Delete();
                }

                Task.Run(() =>
                {
                    _source.CopyTo(_destination, perc => Dispatcher.Invoke(() => progressBar.SetProgress(perc)));
                }).GetAwaiter().OnCompleted(() => MessageBox.Show("File Copied!"));

当我只复制一个文件但我需要复制多个文件时,这非常有效。所以我开始改变一些事情:

    public static void CopyTo(Dictionary<string, string> files, Action<int> progressCallback)
    {
        int globalProgress = 0, globalReportedProgress = 0, globalRead = 0;

        for (var x = 0; x < files.Count; x++)
        {
            var item = files.ElementAt(x);
            var file = new FileInfo(item.Key);
            var destination = new FileInfo(item.Value);

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

    }

当然这段代码有很多错误,但我不明白应该怎么做。

主要目标是为多个磁贴启动单个任务,并为全局复制启用progresscallback。接收字典(它已经在代码的其他部分创建)作为参数。

1 个答案:

答案 0 :(得分:0)

为此我想出了两种方法,一种是在每个文件之后报告进度,另一种是每 n 个字节报告进度。

namespace StackOverflow41750117CopyProgress
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    public class Batch
    {
        private bool _overwrite;

        /// <summary>
        /// Initializes a new instance of the <see cref="Batch"/> class.
        /// </summary>
        /// <param name="overwrite">
        /// True to overwrite the destination file if it already exists (default),
        /// false to throw an exception if the destination file already exists.
        /// </param>
        public Batch(bool overwrite = true)
        {
            this._overwrite = overwrite;
        }

        /// <summary>
        /// Copies the files, reporting progress once per file.
        /// </summary>
        /// <param name="filesToCopy">
        /// A dictionary with the paths of the source files as its keys, and the path to the destination file as its values.
        /// </param>
        /// <param name="progressCallback">
        /// A callback which accepts two Int64 parameters - the number of bytes copied so far, and the total number of bytes to copy.
        /// </param>
        public void CopyReportingPerFile(Dictionary<string, string> filesToCopy, Action<long, long> progressCallback)
        {
            var bytesToCopy = this.GetTotalFileSize(filesToCopy);
            long totalBytesCopied = 0;
            foreach (var copy in filesToCopy)
            {
                File.Copy(copy.Key, copy.Value, this._overwrite);
                totalBytesCopied += new FileInfo(copy.Key).Length;
                progressCallback(totalBytesCopied, bytesToCopy);
            }
        }

        /// <summary>
        /// Copies the files, reporting progress once per read/write operation.
        /// </summary>
        /// <param name="filesToCopy">
        /// A dictionary with the paths of the source files as its keys, and the path to the destination file as its values.
        /// </param>
        /// <param name="progressCallback">
        /// A callback which accepts two Int64 parameters - the number of bytes copied so far, and the total number of bytes to copy.
        /// </param>
        public void CopyReportingPerBuffer(Dictionary<string, string> filesToCopy, Action<long, long> progressCalllback)
        {
            var bytesToCopy = this.GetTotalFileSize(filesToCopy);
            var bufferSize = 1024 * 1024 * 50;
            var buffer = new byte[bufferSize];
            var span = new Span<byte>(buffer);
            long totalBytesCopied = 0;
            foreach (var copy in filesToCopy)
            {
                using (var source = File.OpenRead(copy.Key))
                using (var destination = File.OpenWrite(copy.Value))
                {
                    int bytesRead = 0;
                    do
                    {
                        // The Read method returns 0 once we've reached the end of the file
                        bytesRead = source.Read(span);
                        destination.Write(span);
                        totalBytesCopied += bytesRead;
                        progressCalllback(totalBytesCopied, bytesToCopy);
                    } while (bytesRead > 0);

                    source.Close();
                    destination.Close();
                }
            }
        }

        private long GetTotalFileSize(Dictionary<string, string> filesToCopy)
        {
            long bytesToCopy = 0;
            foreach (var filename in filesToCopy.Keys)
            {
                var fileInfo = new FileInfo(filename);
                bytesToCopy += fileInfo.Length;
            }

            return bytesToCopy;
        }
    }
}

用法:

namespace StackOverflow41750117CopyProgress
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    public class Program
    {
        public static void Main(string[] args)
        {
            var filesToCopy = new Dictionary<string, string>();
            filesToCopy.Add(@"C:\temp\1.mp4", @"C:\temp\1copy.mp4");
            filesToCopy.Add(@"C:\temp\2.mp4", @"C:\temp\2copy.mp4");
            filesToCopy.Add(@"C:\temp\3.mp4", @"C:\temp\3copy.mp4");
            filesToCopy.Add(@"C:\temp\4.mp4", @"C:\temp\4copy.mp4");
            filesToCopy.Add(@"C:\temp\5.mp4", @"C:\temp\5copy.mp4");
            filesToCopy.Add(@"C:\temp\6.mp4", @"C:\temp\6copy.mp4");
            filesToCopy.Add(@"C:\temp\7.mp4", @"C:\temp\7copy.mp4");

            // Make sure the destination files don't already exist
            foreach (var copy in filesToCopy)
            {
                File.Delete(copy.Value);
            }

            var batch = new Batch();
            Console.WriteLine($"Started  {DateTime.Now}");
            batch.CopyReportingPerFile(filesToCopy, (bytesCopied, bytesToCopy) => Console.WriteLine($"Copied {bytesCopied} bytes of {bytesToCopy}"));
            //batch.CopyReportingPerBuffer(filesToCopy, (bytesCopied, bytesToCopy) => Console.WriteLine($"Copied {bytesCopied} bytes of {bytesToCopy}"));
            Console.WriteLine($"Finished {DateTime.Now}");
        }
    }
}

一些观察...

  • 为每个文件报告一次进度更容易实现,但不符合问题的要求,而且如果您复制少量大文件,则响应速度不快。
  • 使用 File.Copy 会保留原始文件的修改日期,将文件读入内存然后将其写入则不会。
  • 将缓冲区大小从 1MB 增加到 10MB,将它们增加到 50MB 会增加内存使用量并提高性能,尽管大部分性能改进似乎是由于在我的 Console.Writeline 中调用 progressCallback不那么频繁,而不是提高磁盘 I/O 的速度。
  • 性能和进度报告频率之间的最佳平衡取决于您的情况和运行该进程的机器的规格,但我发现 50MB 的缓冲区会导致大约每秒一次的进度报告。
  • 注意使用 Span<byte> 而不是 byte[] 用于读取和写入数据的缓冲区 - 这消除了我的代码跟踪文件中当前位置的需要(这是我今天学到的新东西)。

我知道我回答这个问题已经晚了,但希望有人会觉得这很有用。