【问题标题】:Using BinaryWriter or BinaryReader in async code在异步代码中使用 BinaryWriter 或 BinaryReader
【发布时间】:2016-05-26 16:20:48
【问题描述】:

我有一个要写入文件的float 列表。下面的代码可以做到这一点,但它是同步的。

List<float> samples = GetSamples();

using (FileStream stream = File.OpenWrite("somefile.bin"))
using (BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.Default, true))
{
    foreach (var sample in samples)
    {
        binaryWriter.Write(sample);
    }
}

我想异步执行操作,但BinaryWriter 不支持异步操作,这是正常的,因为它每次只写入几个字节。但大多数时候操作使用文件 I/O,我认为它可以而且应该是异步的。

我尝试使用BinaryWriter 写入MemoryStream,完成后我使用CopyToAsyncMemoryStream 复制到FileStream,但这会导致性能下降(总时间)高达100 % 大文件。

如何将整个操作转换为异步操作?

【问题讨论】:

  • 你为什么希望它首先是异步的?当然,首先执行一个 cpu-bound 任务(序列化到内存流)然后执行一个 io-bound 任务(将内存流写入文件)比通过序列化到文件同时执行这两个任务要慢。
  • 能够在不阻塞用户界面的情况下使用该方法。
  • 然后使用Task.Run就可以了。
  • stackoverflow.com/a/46107981/64334查看我的回答

标签: c# async-await binaryreader binarywriter


【解决方案1】:

您的内存流方法很有意义,只需确保分批写入,而不是等待内存流增长到文件的完整大小然后一次全部写入。

这样的东西应该可以正常工作:

var data = new float[10 * 1024];
var helperBuffer = new byte[4096];

using (var fs = File.Create(@"D:\Temp.bin"))
using (var ms = new MemoryStream(4096))
using (var bw = new BinaryWriter(ms))
{
  var iteration = 0;

  foreach (var sample in data)
  {
    bw.Write(sample);

    iteration++;

    if (iteration == 1024)
    {
      iteration = 0;
      ms.Position = 0;

      ms.Read(helperBuffer, 0, 1024 * 4);
      await fs.WriteAsync(helperBuffer, 0, 1024 * 4).ConfigureAwait(false);
    }
  }
}

这只是示例代码 - 请确保正确处理错误等。

【讨论】:

  • 还需要处理循环存在且有数据尚未写入文件的情况。
  • 在循环内执行 await 会破坏异步 I/O 的全部目的——重叠其他有用的操作。
  • 我认为这种方式不会更快(即序列化和写入过程)。您序列化一些对象,然后将它们写出,但在发生这种情况时您不会继续序列化其他对象。如果不需要做其他工作,await 只是无所事事,它不会“预取”下一次迭代......
  • @YacoubMassad 是的,这是大事之一 :) 我不希望这段代码成为你复制粘贴的东西,然后就可以工作了——无论你做什么,它都需要思考。例如,它经过精确调整,仅适用于 1024 * 4 的倍数数据,它不再关心对齐情况(包括您的评论和用于更复杂的事情时的情况)比浮点数组)。
  • @Haukinger 当然,这是另一个很棒的优化。确保仅在 next 迭代结束时等待任务。我相信你会找到很多其他的。不要忘记文件流是缓冲的,所以WriteAsync 倾向于在缓冲区尚未满时立即返回,所以 重叠的。如果缓冲跟不上,那么无论如何您都不会提高吞吐量(尽管调整缓冲区大小是您可能想要做的事情)。
【解决方案2】:

正常的写入操作通常最终都会异步完成。操作系统立即接受写入写入缓存,并在稍后将其刷新到磁盘。您的应用程序不会被实际的磁盘写入阻塞。

当然,如果您正在写入可移动驱动器,则写入缓存通常会被禁用,您的程序将被阻止。


我建议您通过一次传输一个大块来显着减少操作数量。也就是说:

  1. 分配您所需块大小的new T[BlockSize]
  2. 分配一个new byte[BlockSize * sizeof (T)]
  3. 使用List&lt;T&gt;.CopyTo(index, buffer, 0, buffer.Length) 从列表中复制一个批次。
  4. 使用Buffer.BlockCopy 将数据导入byte[]
  5. 通过一次操作将byte[] 写入您的流。
  6. 重复 3-5 直到到达列表末尾。小心最后一批,它可能是部分区块。

【讨论】:

  • 是的,这解释了为什么 OP 的缓冲方法不起作用 - 它通过在 CPU 工作完成后立即推送所有内容来破坏 I/O 缓冲的所有好处。即使您受到 CPU 的限制,您仍然可以从异步 I/O 中获得很多东西 - 例如,保持线程亲和性。
  • 那么,我应该用Task.Run调用这个函数来异步调用它吗?
  • 嗨,Ben,快速提问,这种缓存行为是否也适用于附加到网络流的 BinaryWriter?
  • @BlueStrat:您更有可能在网络共享等慢速 I/O 设备上看到缓存/隐藏的异步性。
【解决方案3】:

有时,这些帮助类毫无帮助。

试试这个:

List<float> samples = GetSamples();

using (FileStream stream = File.OpenWrite("somefile.bin"))
{
    foreach (var sample in samples)
    {
        await stream.WriteAsync(BitConverter.GetBytes(sample), 0, 4);
    }
}

【讨论】:

  • 请注意,这将为大量输入分配很多。如果性能很重要,这可能会有问题。
  • 是的! BinaryWriter 的优点是使用不安全的代码来避免分配。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-21
  • 1970-01-01
  • 1970-01-01
  • 2016-07-27
  • 1970-01-01
  • 2020-02-20
相关资源
最近更新 更多