基本的な考え方は、読み書き用に 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