2

TL/DR:

A と B の 2 台のマシンがあります。テスト プログラムを作成して、それらの間のメディア (インターフェイス) をテストします。ファイルを A から B に、次に B から A にコピーするときのエラーをチェックしますが、最速で実行する必要があります。できます。A ソース ファイル: SRC があり、それを B から新しいファイル: MID にコピーし、MID を B から A に新しいファイル DST にコピーし、SRC と DST を比較します。ここでの問題は、可能な限り最高の速度で(つまり、並行して)それを行う方法です。

手の込んだ:

書き込み中にファイルを同時にコピーするにはどうすればよいですか? CopyFileExを使用して SRC から MID にファイルをコピーし、同時に MID から DST に再度コピーする必要があります。データは明示的にディスクを通過する必要があり、メモリ バッファまたはキャッシュを使用できません。

  1. 2 番目のコピーは、ファイルが 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 コピーを実行するためのスレッドの 2 つのスレッドを作成することで、ある程度高速化できます。もう少し複雑ですが、それほど複雑ではありません。

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!");
}

2 番目のスレッドでの読み取りと書き込みは、最初のスレッドでの読み取りと書き込みと大幅にオーバーラップする可能性があるため、これにより処理速度が大幅に向上します。ほとんどの場合、MID が保存されているディスクの速度が制限要因になります。

非同期書き込みを行うことで、速度を上げることができます。つまり、スレッドにバッファを読み取らせてから、非同期書き込みを開始させます。その書き込みが実行されている間、次のバッファが読み取られています。そのスレッドで別の非同期書き込みを開始する前に、非同期書き込みが完了するのを待つことを忘れないでください。したがって、各スレッドは次のようになります。

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

MID ファイルへの同時アクセスが制限されているため、パフォーマンスがどの程度向上するかはわかりません。しかし、それはおそらく試してみる価値があります。

そこにある同期コードにより、2 番目のスレッドが読み取りを試みてはならないときに読み取れないことがわかっています。最初のスレッドが終了した後にシグナルを待っているために 2 番目のスレッドがロックアップする状況を防ぐことができると思います。疑問がある場合はManualResetEvent、最初のスレッドが完了したことを示すために使用される を使用WaitHandle.WaitAnyし、それを待機するために を使用するか、次のようAutoResetEventに でタイムアウトを使用することができます。WaitOne

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