将(多个)文件复制到多个位置

Copy (multiple) files to multiple locations

使用 C# (.NET 4.5) 我想将一组文件复制到多个位置(例如,将文件夹的内容复制到连接到计算机的 2 个 USB 驱动器)。
有没有比使用 foreach 循环和 File.Copy?

更有效的方法呢?

正在努力寻找(可能的)解决方案。

我的第一个想法是某种多线程方法。经过一些阅读和研究后,我发现就 IO 而言,只是盲目地设置某种并行 and/or 异步进程并不是一个好主意(根据 Why is Parallel.ForEach much faster then AsParallel().ForAll() even though MSDN suggests otherwise?)。

瓶颈是磁盘,特别是如果它是传统驱动器,因为它只能 read/write 同步。这让我想到,如果我只读一次然后在多个位置输出它会怎么样?毕竟,在我的 USB 驱动器场景中,我正在处理多个(输出)磁盘。

虽然我不知道该怎么做。我认为我看到的一个 (Copy same file from multiple threads to multiple destinations) 是将每个文件的所有字节读入内存,然后循环遍历目标并将字节写出到每个位置,然后再移动到下一个文件。如果文件可能很大,这似乎是个坏主意。我将要复制的一些文件是视频,可能有 1 GB(或更多)。我无法想象将 1 GB 的文件加载到内存中只是为了将其复制到另一个磁盘是个好主意吗?

因此,考虑到较大文件的灵活性,我得到的最接近的文件如下(基于 How to copy one file to many locations simultaneously)。这段代码的问题是我仍然没有发生单读和多写。目前是多读多写。有没有办法进一步优化这段代码?我可以将块读入内存,然后将该块写入每个目的地,然后再移动到下一个块(就像上面的想法,但分块文件而不是整个文件)?

files.ForEach(fileDetail =>
    Parallel.ForEach(fileDetail.DestinationPaths, new ParallelOptions(),
        destinationPath =>
        {
            using (var source = new FileStream(fileDetail.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var destination = new FileStream(destinationPath, FileMode.Create))
            {
                var buffer = new byte[1024];
                int read;

                while ((read = source.Read(buffer, 0, buffer.Length)) > 0)
                {
                    destination.Write(buffer, 0, read);
                }
            }
        }));

IO 操作通常应被视为 asynchronous,因为有些硬件操作 运行 在您的代码之外,因此您可以尝试为 read/write操作,所以你可以在硬件操作期间继续执行。

while ((read = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
    await destination.WriteAsync(buffer, 0, read);
}

您还必须将您的 lambda 委托标记为 async 才能使此工作正常进行:

async destinationPath => 
...

而且你应该一直等待结果任务。您可以在这里找到更多信息:

Parallel foreach with asynchronous lambda

Nesting await in Parallel.ForEach

我想我会 post 我目前的解决方案,供遇到此问题的其他人使用。

如果有人发现更多 efficient/quicker 的方法,请告诉我!

我的代码似乎比 运行 同步复制文件要快一点,但它仍然没有我想要的那么快(也没有我看到的其他程序那么快) .我应该注意,性能可能会因 .NET 版本和您的系统而异(我在 13" MBP 和 2.9GHz i5(5287U - 2 核/4 线程)+ 16GB RAM 上使用 Win 10 和 .NET 4.5.2) .我什至还没有想出方法(例如FileStream.WriteFileStream.WriteAsyncBinaryWriter.Write)和缓冲区大小的最佳组合。

foreach (var fileDetail in files)
{
    foreach (var destinationPath in fileDetail.DestinationPaths)
        Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));

    // Set up progress
    FileCopyEntryProgress progress = new FileCopyEntryProgress(fileDetail);

    // Set up the source and outputs
    using (var source = new FileStream(fileDetail.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan))
    using (var outputs = new CompositeDisposable(fileDetail.DestinationPaths.Select(p => new FileStream(p, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize))))
    {
        // Set up the copy operation
        var buffer = new byte[bufferSize];
        int read;

        // Read the file
        while ((read = source.Read(buffer, 0, buffer.Length)) > 0)
        {
            // Copy to each drive
            await Task.WhenAll(outputs.Select(async destination => await ((FileStream)destination).WriteAsync(buffer, 0, read)));

            // Report progress
            if (onDriveCopyFile != null)
            {
                progress.BytesCopied = read;
                progress.TotalBytesCopied += read;

                onDriveCopyFile.Report(progress);
            }
        }
    }

    if (ct.IsCancellationRequested)
        break;
}

我正在使用 Reactive Extensions (https://github.com/Reactive-Extensions/Rx.NET) 中的 CompositeDisposable