2

TL/DR:

我有两台机器:A 和 B。我制作了一个测试程序,以测试它们之间的介质(接口)——我在将文件从 A 复制到 B 再从 B 复制到 A 时检查错误,但我必须以最快的速度完成我可以。所以我有一个源文件:SRC,我将它复制到 B 到新文件:MID,然后我再次将 MID 从 B 复制到 A 到新文件 DST,然后我将 SRC 与 DST 进行比较。这里的问题是如何以尽可能高的速度(即并行)进行

精心制作的:

如何在写入文件时同时复制文件?我使用CopyFileEx将文件从 SRC 复制到 MID,我必须同时将它从 MID 再次复制到 DST。数据必须显式通过磁盘,我不能使用内存缓冲区或缓存,并且:

  1. 必须在 MID 上创建文件时执行第二次复制 - 我等不及它完成复制。
  2. 我必须再次明确地从 MID 读取文件 - 我不能使用我用来从 SRC 复制到 MID 的缓冲区
  3. 所有这一切都必须以最快的速度执行

我可以轻松处理同步问题(我使用CopyFileExCopyProgressRoutine回调来了解完成了多少字节并相应地触发事件),但是文件在被复制时被锁定以供读取。我不能使用普通的 C# 的FileStream - 它太慢了......

我目前正在研究的可能解决方案:

  • 卷影复制(特别是AlphaVSS
  • memory-mapped-file - 我设法做到非常快,但我担心系统实际上使用缓存,并没有真正从 MID 读回
  • 一些我不知道的win-API P/Invoke 函数??
4

2 回答 2

1

为了能够在写入文件时读取文件,它必须是用dwShareMode = FILE_SHARE_READ. 您可能不得不放弃并使用/ /CopyFileEx自己实现它。对于异步读/写,您可以使用/函数的参数。CreateFileReadFileWriteFilelpOverlappedReadFileWriteFile

于 2013-07-02T10:39:55.027 回答
1

基本思路是打开MID文件进行读写。简单的单线程方法是:

private static void FunkyCopy(string srcFname, string midFname, string dstFname)
{
    using (FileStream srcFile = new FileStream(srcFname, FileMode.Open, FileAccess.Read, FileShare.None),
                        midFile = new FileStream(midFname, FileMode.Create, FileAccess.ReadWrite,
                                                FileShare.ReadWrite),
                        dstFile = new FileStream(dstFname, FileMode.Create, FileAccess.Write, FileShare.None))
    {
        long totalBytes = 0;
        var buffer = new byte[65536];
        while (totalBytes < srcFile.Length)
        {
            var srcBytesRead = srcFile.Read(buffer, 0, buffer.Length);
            if (srcBytesRead > 0)
            {
                // write to the mid file
                midFile.Write(buffer, 0, srcBytesRead);
                // now read from mid and write to dst
                midFile.Position = totalBytes;
                var midBytesRead = midFile.Read(buffer, 0, srcBytesRead);
                if (midBytesRead != srcBytesRead)
                {
                    throw new ApplicationException("Error reading Mid file!");
                }
                dstFile.Write(buffer, 0, srcBytesRead);
            }
            totalBytes += srcBytesRead;
        }
    }
}

正如您所指出的,这将非常缓慢。您可以通过创建两个线程来加快速度:一个用于执行 SRC -> MID 复制,另一个用于执行 MID -> DST 复制。它涉及更多一点,但并非如此。

static void FunkyCopy2(string srcFname, string midFname, string dstFname)
{
    var cancel = new CancellationTokenSource();
    const int bufferSize = 65536;

    var finfo = new FileInfo(srcFname);
    Console.WriteLine("File length = {0:N0} bytes", finfo.Length);
    long bytesCopiedToMid = 0;
    AutoResetEvent bytesAvailable = new AutoResetEvent(false);

    // First thread copies from src to mid
    var midThread = new Thread(() =>
        {
            Console.WriteLine("midThread started");
            using (
                FileStream srcFile = new FileStream(srcFname, FileMode.Open, FileAccess.Read, FileShare.None),
                            midFile = new FileStream(midFname, FileMode.Create, FileAccess.Read,
                                                    FileShare.ReadWrite))
            {
                var buffer = new byte[bufferSize];
                while (bytesCopiedToMid < finfo.Length)
                {
                    var srcBytesRead = srcFile.Read(buffer, 0, buffer.Length);
                    if (srcBytesRead > 0)
                    {
                        midFile.Write(buffer, 0, srcBytesRead);
                        Interlocked.Add(ref bytesCopiedToMid, srcBytesRead);
                        bytesAvailable.Set();
                    }
                }
            }
            Console.WriteLine("midThread exit");
        });

    // Second thread copies from mid to dst
    var dstThread = new Thread(() =>
        {
            Console.WriteLine("dstThread started");
            using (
                FileStream midFile = new FileStream(midFname, FileMode.Open, FileAccess.Read,
                                                    FileShare.ReadWrite),
                            dstFile = new FileStream(dstFname, FileMode.Create, FileAccess.Write, FileShare.Write)
                )
            {
                long bytesCopiedToDst = 0;
                var buffer = new byte[bufferSize];
                while (bytesCopiedToDst != finfo.Length)
                {
                    // if we've already copied everything from mid, then wait for more.
                    if (Interlocked.CompareExchange(ref bytesCopiedToMid, bytesCopiedToDst, bytesCopiedToDst) ==
                        bytesCopiedToDst)
                    {
                        bytesAvailable.WaitOne();
                    }
                    var midBytesRead = midFile.Read(buffer, 0, buffer.Length);
                    if (midBytesRead > 0)
                    {
                        dstFile.Write(buffer, 0, midBytesRead);
                        bytesCopiedToDst += midBytesRead;
                        Console.WriteLine("{0:N0} bytes copied to destination", bytesCopiedToDst);
                    }
                }
            }
            Console.WriteLine("dstThread exit");
        });

    midThread.Start();
    dstThread.Start();

    midThread.Join();
    dstThread.Join();
    Console.WriteLine("Done!");
}

这会大大加快速度,因为第二个线程中的读取和写入可以在很大程度上与第一个线程中的读取和写入重叠。最有可能的是,您的限制因素将是存储 MID 的磁盘的速度。

您可以通过执行异步写入来提高速度。也就是说,让线程读取缓冲区,然后触发异步写入。在执行该写入时,正在读取下一个缓冲区。只需记住等待异步写入完成,然后再在该线程中开始另一个异步写入。所以每个线程看起来像:

while (bytes left to copy)
    Read buffer
    wait for previous write to finish
    write buffer
end while

我不知道这会给你带来多大的性能提升,因为你被限制在对 MID 文件的并发访问上。但这可能值得尝试。

我知道那里的同步代码会阻止第二个线程在不应该读取的时候尝试读取。我认为这将防止第二个线程锁定的情况,因为它在第一个线程退出后等待信号。如果有任何疑问,您可以使用一个ManualResetEvent用来表示第一个线程已完成,并使用WaitHandle.WaitAny它来等待它和AutoResetEvent,或者您可以在 上使用超时WaitOne,如下所示:

bytesAvailable.WaitOne(1000); // waits a second before trying again
于 2013-07-03T20:44:43.037 回答