【问题标题】:How does asynchronous file hash and disk write actually work?异步文件哈希和磁盘写入实际上是如何工作的?
【发布时间】:2018-07-31 13:10:37
【问题描述】:

我正在构建一个 ASP.NET Core 应用程序,它需要处理高达 200GB 的大文件上传。我的目标是将这些文件写入磁盘并同时捕获 MD5 哈希。

我已经完成并创建了自己的方法来识别来自 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 是将字节保存在内存中还是只是在它们经过时读取它们?
  • cryptoStreamtargetStream 是否异步发生?
  • 还是异步复制到cryptoStream,同步写入到targetStream

我很高兴这行得通,但在没有完全理解的情况下,我担心我引入了一些邪恶的东西。

【问题讨论】:

    标签: c# asp.net-core async-await md5 filestream


    【解决方案1】:

    它是这样工作的:

    1) CopyToAsync 分配指定大小的字节缓冲区(如果您使用类似问题的重载,则分配默认大小)。然后它在源流上调用ReadAsync 以填充该缓冲区,然后在目标流上调用WriteAsync 将该缓冲区写入目标流。重复直到写入所有数据。所以这个操作将小字节数组(缓冲区)保存在内存中。读写是异步的(如果源\目标流支持)。

    2) CryptoStream 在写入模式下的工作方式如下:当您写入时,它会获取您写入的缓冲区(与上面讨论的缓冲区相同)并将其提供给您传递给它的 ICryptoTransform 实现(在这种情况下) -MD5)。转换可能需要处理特定大小的块(由ICryptoTransform.InputBlockSize 属性确定)。在这种情况下,CryptoStream 可能会稍微缓存您写入它的数据,直到有特定大小的完整块。这不是问题,因为这些块通常非常小(远小于CopyAsync 的合理缓冲区大小)。然后它将这些块一个一个地传递给ICryptoTransform.TransformBlock,并接收输出(另一个字节数组)。这个过程是同步的,因为这里没有任何东西可以是异步的。

    3) 块被ICryptoTransform 转换后 - 此块被异步写入输出流(在这种情况下为targetStream)(使用WriteAsync)。所以CryptoStream的内存消耗也很小,和目标转换输入输出块大小有关。

    4) MD5 的实现 ICryptoTransform 使用传递的块来连续计算哈希,因为该算法不需要一次完整的数据来计算哈希,它可以逐块计算它。然后它输出与输入时完全相同的块,因此没有进行任何转换。这意味着 MD5 的 TransformBlock 只是按原样返回输入,同时在内部更新哈希。

    总结并回答您的问题:

    • crypto stream 只保留小缓冲区来缓冲数据,直到转换输入块大小,它会尽快将转换后的数据直接写入输出流。它不保存整个数据的副本。
    • 加密流本身没有 IO 工作,它只执行 CPU 绑定工作(转换),这应该是同步发生的。但是,当您写入加密流时(它会写入目标流),这确实是异步发生的。

    旁注 - 要真正利用异步文件 IO - 您需要使用“异步”选项初始化文件流,例如:

    new FileStream(pathAndFileName, FileMode.Create, FileAccess.Write, FileShare.None,
                   4096, FileOptions.Asynchronous)
    

    否则,即使使用WriteAsync,您对目标流的写入也会同步。

    【讨论】:

    • 感谢您提供如此详尽的回答。加密流是否需要相同的异步初始化?
    • @ahsteele 不,这种初始化是 FileStream 独有的(因为底层的 windows api 是这样工作的)。另外,CryptoStream 无论如何都没有异步 IO(甚至任何 IO),它只是代理对目标流的异步调用。
    猜你喜欢
    • 2011-04-05
    • 2010-09-28
    • 1970-01-01
    • 1970-01-01
    • 2012-02-19
    • 2011-08-29
    • 1970-01-01
    • 2014-10-30
    • 1970-01-01
    相关资源
    最近更新 更多