基本思路是打开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