我有两台机器:A和B.我制作一个测试程序,测试它们之间的介质(接口) - 我在将文件从A复制到B然后从B复制到A时检查错误,但我必须做它是我能做的最快的。所以我有一个源文件:SRC,我将它复制到B到新文件:MID,然后我再次将MID从B复制到A到新文件DST,然后我将SRC与DST进行比较。这里的问题是如何以尽可能高的速度(即并行)来实现它
如何在文件写入时同时复制文件?我使用CopyFileEx将文件从SRC复制到MID,我必须同时将它从MID复制到DST。数据必须明确地通过磁盘,我不能使用内存缓冲区或缓存,并且:
我可以轻松处理同步问题(我使用CopyFileEx的CopyProgressRoutine回调来知道完成了多少字节并相应地触发事件),但文件被锁定以便在复制时进行读取。我不能使用普通的C#FileStream - 这太慢了......
我目前正在研究的可能解决方案:
答案 0 :(得分:1)
为了能够在文件写入时读取文件,必须使用dwShareMode = FILE_SHARE_READ
创建文件。您可能不得不放弃CopyFileEx
并使用CreateFile
/ ReadFile
/ WriteFile
自行实施。对于异步读/写,您可以使用lpOverlapped
/ ReadFile
函数的WriteFile
参数。
答案 1 :(得分: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 - &gt; MID副本,另一个用于执行MID - &gt; 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