异步文件哈希和磁盘写入实际上是如何工作的?
How does asynchronous file hash and disk write actually work?
我正在构建一个 ASP.NET 核心应用程序,它需要处理大文件上传——多达 200GB。我的目标是将这些文件写入磁盘并同时捕获一个 MD5 Hash。
我已经完成并创建了自己的方法来识别来自 HTTP 客户端请求的文件流,如 Uploading large files with streaming 中所述。找到流后,我将使用以下代码写入磁盘并创建 MD5 哈希。
// removed the curly brackets from using statements for readability on Stack Overflow
var md5 = MD5.Create();
using (var targetStream = File.OpenWrite(pathAndFileName))
using (var cryptoStream = new CryptoStream(targetStream, md5, CryptoStreamMode.Write))
using (var sourceStream = fileNameAndStream.FileStream)
{
await sourceStream.CopyToAsync(cryptoStream);
}
var hash = md5.Hash;
md5.Dispose();
很棒的是上面的工作(文件创建和散列生成)。不太棒的是我不完全理解它是如何工作的:
- 是否正在将
cryptoStream
复制到 targetStream
然后写入到 targetStream
?
cryptoStream
是将字节保存在内存中还是只是在它们经过时读取它们?
cryptoStream
和 targetStream
是否异步发生?
- 或者是对
cryptoStream
的异步复制和对 targetStream
的同步写入?
我很高兴这个工作,但在没有完全理解它的情况下,我担心我引入了一些邪恶的东西。
它是这样工作的:
1) CopyToAsync
分配指定大小的字节缓冲区(或者如果您使用有问题的重载,则使用默认大小)。然后它在源流上调用 ReadAsync
来填充该缓冲区,然后在目标流上调用 WriteAsync
将该缓冲区写入目标流。重复直到写入所有数据。所以这个操作在内存中保存小字节数组(缓冲区)。读写是异步的(如果 source\target 流支持)。
2) CryptoStream
在写入模式下是这样工作的:当你写入它时,它需要你写入的缓冲区(这与上面讨论的缓冲区相同)并将它提供给你传递的 ICryptoTransform
实现到它(在这种情况下 - MD5
)。转换可能需要以特定大小的块进行处理(由 ICryptoTransform.InputBlockSize
属性 确定)。在这种情况下,CryptoStream
可能会缓存您写入的数据,直到有特定大小的完整块。这不是问题,因为这些块通常非常小(远小于 CopyAsync
的合理缓冲区大小)。然后它将这些块一个一个地传递给 ICryptoTransform.TransformBlock
,并接收输出(另一个字节数组)。这个过程是同步的,因为这里没有任何东西可以异步。
3) 在块被 ICryptoTransform
转换后 - 此块被异步写入输出流(在本例中为 targetStream
)(使用 WriteAsync
)。所以CryptoStream
的内存消耗也很小,和target transform input and output block size有关。
4) MD5
ICryptoTransform
的实现使用传递的块来连续计算哈希值,因为该算法不需要立即存在完整数据来计算哈希值,它可以通过块计算它堵塞。然后它输出与输入时接收到的完全相同的块,因此没有进行任何转换。这意味着 TransformBlock
对于 MD5 只是 returns 输入,同时在内部更新散列。
总结并回答您的问题:
- crypto stream 只持有小缓冲区来缓冲数据,直到转换输入块大小,它尽快将转换后的数据直接写入输出流。它不包含整个数据的副本。
- 加密流本身没有 IO 工作发生,它只执行 CPU 绑定工作(转换),这是同步发生的,它应该。但是当你写入加密流时——它写入目标流——并且这确实是异步发生的。
旁注 - 要真正利用异步文件 IO - 您需要使用 "asynchronous" 选项初始化文件流,例如:
new FileStream(pathAndFileName, FileMode.Create, FileAccess.Write, FileShare.None,
4096, FileOptions.Asynchronous)
否则,即使使用 WriteAsync
,您对目标流的写入也将是同步的。
我正在构建一个 ASP.NET 核心应用程序,它需要处理大文件上传——多达 200GB。我的目标是将这些文件写入磁盘并同时捕获一个 MD5 Hash。
我已经完成并创建了自己的方法来识别来自 HTTP 客户端请求的文件流,如 Uploading large files with streaming 中所述。找到流后,我将使用以下代码写入磁盘并创建 MD5 哈希。
// removed the curly brackets from using statements for readability on Stack Overflow
var md5 = MD5.Create();
using (var targetStream = File.OpenWrite(pathAndFileName))
using (var cryptoStream = new CryptoStream(targetStream, md5, CryptoStreamMode.Write))
using (var sourceStream = fileNameAndStream.FileStream)
{
await sourceStream.CopyToAsync(cryptoStream);
}
var hash = md5.Hash;
md5.Dispose();
很棒的是上面的工作(文件创建和散列生成)。不太棒的是我不完全理解它是如何工作的:
- 是否正在将
cryptoStream
复制到targetStream
然后写入到targetStream
? cryptoStream
是将字节保存在内存中还是只是在它们经过时读取它们?cryptoStream
和targetStream
是否异步发生?- 或者是对
cryptoStream
的异步复制和对targetStream
的同步写入?
我很高兴这个工作,但在没有完全理解它的情况下,我担心我引入了一些邪恶的东西。
它是这样工作的:
1) CopyToAsync
分配指定大小的字节缓冲区(或者如果您使用有问题的重载,则使用默认大小)。然后它在源流上调用 ReadAsync
来填充该缓冲区,然后在目标流上调用 WriteAsync
将该缓冲区写入目标流。重复直到写入所有数据。所以这个操作在内存中保存小字节数组(缓冲区)。读写是异步的(如果 source\target 流支持)。
2) CryptoStream
在写入模式下是这样工作的:当你写入它时,它需要你写入的缓冲区(这与上面讨论的缓冲区相同)并将它提供给你传递的 ICryptoTransform
实现到它(在这种情况下 - MD5
)。转换可能需要以特定大小的块进行处理(由 ICryptoTransform.InputBlockSize
属性 确定)。在这种情况下,CryptoStream
可能会缓存您写入的数据,直到有特定大小的完整块。这不是问题,因为这些块通常非常小(远小于 CopyAsync
的合理缓冲区大小)。然后它将这些块一个一个地传递给 ICryptoTransform.TransformBlock
,并接收输出(另一个字节数组)。这个过程是同步的,因为这里没有任何东西可以异步。
3) 在块被 ICryptoTransform
转换后 - 此块被异步写入输出流(在本例中为 targetStream
)(使用 WriteAsync
)。所以CryptoStream
的内存消耗也很小,和target transform input and output block size有关。
4) MD5
ICryptoTransform
的实现使用传递的块来连续计算哈希值,因为该算法不需要立即存在完整数据来计算哈希值,它可以通过块计算它堵塞。然后它输出与输入时接收到的完全相同的块,因此没有进行任何转换。这意味着 TransformBlock
对于 MD5 只是 returns 输入,同时在内部更新散列。
总结并回答您的问题:
- crypto stream 只持有小缓冲区来缓冲数据,直到转换输入块大小,它尽快将转换后的数据直接写入输出流。它不包含整个数据的副本。
- 加密流本身没有 IO 工作发生,它只执行 CPU 绑定工作(转换),这是同步发生的,它应该。但是当你写入加密流时——它写入目标流——并且这确实是异步发生的。
旁注 - 要真正利用异步文件 IO - 您需要使用 "asynchronous" 选项初始化文件流,例如:
new FileStream(pathAndFileName, FileMode.Create, FileAccess.Write, FileShare.None,
4096, FileOptions.Asynchronous)
否则,即使使用 WriteAsync
,您对目标流的写入也将是同步的。