【问题标题】:Sending a large object to a remote server using Streams and HttpClient使用 Streams 和 HttpClient 将大对象发送到远程服务器
【发布时间】:2023-02-20 00:01:06
【问题描述】:

我的 C# 代码中有一个大对象,它可以大到 15GB。在内部,它有一个双精度二维数组和 2 个描述二维数组的行和列的字符串列表。

这个对象有一个方法WriteToTextWriter(StreamWriters),它将二维数组中的一个头和整个数据写入StreamWriters。 StreamWriter 是使用 MemoryStream 对象初始化的。

我有另一个类使用 HttpClient 将数据从 Stream 发布到远程服务器。 它有一个方法PostStreamData(string URL, Stream s)

我当前的代码是这样的:

var x = MyLargeObject();
using (var memStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memStream))
{
    x.WriteToTextWriter(streamWriter);
    customClient.PostStreamData(url, memStream);
}

在内部,PostStreamData使用传入的流对象创建一个StreamContent(),将此内容设置为HttpRequestMessage对象的Content属性,最后使用SenAsync方法发送。

由于这使用了MemoryStream,因此当对象大小大于 2GB 时它会失败。看到这个:Failed to write large amount of data to stream

为了克服这个问题,我使用了在那里实现的 HugeMemoryStream 类。但现在的问题是我使用了两倍的内存。 15GB 用于 MyLargeObjet 已经在内存中,然后另外 15GB 用于使用它创建的 HugeMemoryStream 对象。

我认为更好的解决方案是实现一个基于 Stream 的类,它使用大小有限的缓冲区但仍然允许大于 2GB 的对象。如何实施?我正在寻找一些示例代码。它不一定是完整的,但现在我什至不知道如何开始。

【问题讨论】:

  • 如果你想减少内存使用,不要使用内存流。无论如何,您可能希望在许多较小的请求中通过 http 发送数据。
  • 我看到几个小错别字:SenAcyncMyLargeObjet
  • 那时我遇到了类似的问题。目标设备是一个小型 arm64 设备,具有较小的 ram,但它有足够的存储空间。因此,每当我们必须向该设备发送更新时,我们必须将字节数组拆分为多个包。我们发送给设备的第一条消息是;它应该期望多少包和千字节。收到每个包裹后,它会检查包裹是否都在。如果是;我们连接了收到的包裹。这是处理它的一种方法。
  • 您是否检查过为什么要分配额外的内存,我的盲目猜测是您正在内存流中分配字符串。 int 和 float 的字符串表示将比二进制表示占用更多空间。如果首先加载二进制对象并创建字符串,您将在内存中拥有原始副本和膨胀副本
  • 为什么不能直接写入 HttpClient 中的响应流?那应该消除对额外内存流的需要

标签: c# .net algorithm http memory


【解决方案1】:

您可以继承 Stream 并保留对 MyLargeObject 的引用。然后实现 Read 方法,将大对象序列化为 Read 的字节数组参数。你必须实施 Canseek,canwrite 你刚刚返回 false 的地方。其他方法只是抛出不支持的异常。你会像这样使用它:

var content = new StreamContent(new MyStream(mylargeobject))

另请查看此实现: https://ec.europa.eu/digital-building-blocks/code/projects/EDELIVERY/repos/eessi-as4.net/browse/source/AS4/Eu.EDelivery.AS4/Streaming/VirtualStream.cs?at=a37db0be60a5c441fdb6c9d65f7c4c4621840b92

【讨论】:

  • > 在哪里将大对象序列化为 Read @bgman 的字节数组参数 你能解释一下这部分吗? Memorystream 使用一维字节数组作为缓冲区,那么如何将一个 15GB 的对象序列化到这个数组中呢?
  • MyStream 有一个对 LargeObject 的引用,并且您实现了 Read 方法,您必须在该方法中将数组参数从当前位置开始放置多个字节。检查 Stream.Read 的文档。我只是说,你只需要实现 Read 方法,但这可能是一项艰巨的任务
  • 我正在与一位同事讨论这个问题,他也提出了你告诉我的内容,但我不明白这将如何减少内存使用。我锅里的代码有这样一行:x.WriteToTextWriter(streamWriter);writeToTextWriter方法将整个15GB的数据写入StreamWriter。这也需要一些修改吗?
  • 如我所示,您可以直接使用自定义流来创建 StreamContent。如果您使用 WritetoTextWriter 写入流,那么您将拥有 2 个大对象:对象本身和流,正如您所指出的。自定义流不会增加内存,因为它只包含对大对象的引用。仍然实现读取方法并不容易,您应该适配 writetotextwriter。这并不容易,因为您必须跟踪当前位置。例如:第一次读取读取 1000 个字节,现在在第二次读取时你必须给出接下来的 1000 个字节,
【解决方案2】:

我认为使用内存来管理这样的数据根本不是一个好主意,您最好将其写入磁盘,然后通过具有并行上传和重试逻辑的单独服务将其发布。

哦,如果你也在处理像这样的大二进制文件,可能想看看使用某种差异算法来生成补丁文件并改用它们,比如 bsdiff 或 xdelta。

Useful Binary Diff Tool (other than msdn[apatch and mpatch], xdelta, bsdiff, vbindiff and winmerge)

【讨论】:

    【解决方案3】:

    使用 PushStreamContent(在 System.Net.Http.Formatting.dll 中):

    var x = MyLargeObject();
    var content = new PushStreamContent((stream, httpContent, transportContext) =>
    {
        using (var streamWriter = new StreamWriter(stream))
            x.WriteToTextWriter(streamWriter);  
        stream.Close();
    });
    
    customClient.PostStreamData(url, content);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-05
      • 1970-01-01
      • 2021-07-06
      • 1970-01-01
      • 2019-01-15
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多