【问题标题】:Streaming large list of data as JSON format using Json.net使用 Json.net 将大量数据流式传输为 JSON 格式
【发布时间】:2014-12-03 20:29:02
【问题描述】:

使用 MVC 模型,我想编写一个 JsonResult,它将 Json 字符串流式传输到客户端,而不是一次将所有数据转换为 Json 字符串,然后将其流式传输回客户端。 我有一些动作需要在 Json 传输时发送非常大(超过 300,000 条记录),我认为基本的 JsonResult 实现是不可扩展的。

我正在使用 Json.net,我想知道是否有一种方法可以在转换 Json 字符串时对其进行流式传输。

//Current implementation:
response.Write(Newtonsoft.Json.JsonConvert.SerializeObject(Data, formatting));
response.End();

//I know I can use the JsonSerializer instead
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Serialize(textWriter, Data);

但是我不确定如何将块写入 textWriter 并写入响应并调用 reponse.Flush() 直到所有 300,000 条记录都转换为 Json。

这可能吗?

【问题讨论】:

    标签: c# json asp.net-mvc json.net


    【解决方案1】:

    假设您的最终输出是一个 JSON 数组,并且每个“块”是该数组中的一项,您可以尝试类似下面的 JsonStreamingResult 类。它使用 JsonTextWriter 将 JSON 写入输出流,并使用 JObject 作为在将每个项目写入写入器之前单独序列化每个项目的方法。您可以传递 JsonStreamingResultIEnumerable 实现,它可以从您的数据源中单独读取项目,这样您就不会一次将它们全部存储在内存中。我没有对此进行广泛的测试,但它应该能让你朝着正确的方向前进。

    public class JsonStreamingResult : ActionResult
    {
        private IEnumerable itemsToSerialize;
    
        public JsonStreamingResult(IEnumerable itemsToSerialize)
        {
            this.itemsToSerialize = itemsToSerialize;
        }
    
        public override void ExecuteResult(ControllerContext context)
        {
            var response = context.HttpContext.Response;
            response.ContentType = "application/json";
            response.ContentEncoding = Encoding.UTF8;
    
            JsonSerializer serializer = new JsonSerializer();
    
            using (StreamWriter sw = new StreamWriter(response.OutputStream))
            using (JsonTextWriter writer = new JsonTextWriter(sw))
            {
                writer.WriteStartArray();
                foreach (object item in itemsToSerialize)
                {
                    JObject obj = JObject.FromObject(item, serializer);
                    obj.WriteTo(writer);
                    writer.Flush();
                }
                writer.WriteEndArray();
            }
        }
    }
    

    【讨论】:

    • 该解决方案有效,可以防止内存不足异常,这太棒了。但我认为,如果将成批的记录一起刷新而不是一个一个地刷新,它会更加优化。不确定最佳数字是多少!
    • 是的,我也想知道。您可以轻松地向 JsonStreamingResult 添加一个计数器,使其等待刷新,直到从可枚举中读取了一些记录。如果数字因情况而异,您可以将其设置为参数,以便您可以针对每种不同的用途对其进行调整。此外,在 IEnumerable 方面,您还可以实现一种机制来批量查询数据源,以提高那里的效率。不过,您必须进行大量测量和测试才能看到最有效的方法。
    • 另一个想法虽然可能不可能是测量缓冲区大小并以每 64KB 或类似的方式刷新。不确定我们是否可以检查 JsonTextWriter 中的数据大小
    • 如果你想做类似的事情,你可以尝试用BufferedStream 包裹OutputStream。但是,this Q & A 似乎表明 .NET 中的大多数流在缓冲方面已经得到了很好的优化。如果是这种情况,也许最好不要调用Flush,而让流在其内部缓冲区已满时执行其操作。不过不确定;你必须测试它。
    • 一些基准测试表明最有效的方法是使用 serializer.Serialize(writer, data);并一次将所有数据传递给它,因为上面的注释表明 Stream 本身在处理缓冲区方面做得很好,你的代码不需要做一个巨大的循环:)
    【解决方案2】:

    将其留给 .NET 并等待缓冲区已满的问题还有其他问题。

    例如: 如果你这样做,一些 json 的内容将被切断,从而导致前端的解析问题。

    到目前为止,最好的方法是在您使用批次的情况下在每次迭代时刷新批次,或者如果您的设计是为了这样做,则按单个项目刷新它。

    目前我使用 SSE 将数据推送到浏览器并使用分隔符消息“在消息结束时”向浏览器指示连接可以关闭,我知道 SSE 用例用于连续流,但我们也可以使用它帮助分块和批处理响应。

    【讨论】:

      猜你喜欢
      • 2012-06-23
      • 2018-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多