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

时间:2017-01-09 01:48:13

标签: c# .net multithreading file-io file-copying

使用C#(.NET 4.5)我想将一组文件复制到多个位置(例如,将文件夹的内容复制到连接到计算机的2个USB驱动器)。
有没有更有效的方法,然后只使用foreach循环和string

努力实现(可能的)解决方案。

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

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

我无法弄清楚如何做到这一点。我看到的一个想法(Copy same file from multiple threads to multiple destinations)只是将每个文件的所有字节读入内存然后遍历目的地并将字节写出到每个位置,然后转移到下一个文件。如果文件可能很大,那似乎是一个坏主意。我要复制的一些文件将是视频,可能是1 GB(或更多)。我无法想象将1 GB文件加载到内存中只是为了将其复制到另一个磁盘上是个好主意吗?

因此,允许更大文件的灵活性,我得到的最接近的文件(基于How to copy one file to many locations simultaneously)。这段代码的问题在于我还没有一次读取和多次写入。它目前是多读和多写的。有没有办法进一步优化此代码?我可以将块读入内存然后将该块写入每个目标,然后再转移到下一个块(就像上面的想法一样,但是整块文件而不是整个)?

File.Copy

2 个答案:

答案 0 :(得分:2)

一般情况下,IO操作应被视为asynchronous,因为有些硬件操作在代码之外运行,因此您可以尝试引入一些async/await构造用于读/写操作,因此您可以在硬件操作期间继续执行。

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

答案 1 :(得分:1)

我认为我会向遇到此问题的其他人发布我当前的解决方案。

如果有人发现更有效/更快捷的方式,请告诉我们!

我的代码似乎比同步运行副本更快地复制文件但它仍然没有我想要的那么快(也没有我见过的其他程序那么快它)。我应该注意,性能可能会有所不同,具体取决于.NET版本和您的系统(我在13" MBP和2.9GHz i5(5287U - 2核/ 4线程)上使用Win 10和.NET 4.5.2 + 16GB RAM)。我甚至没有找到方法的最佳组合(例如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

相关问题