【问题标题】:Json.net Async when writing to File写入文件时的 Json.net 异步
【发布时间】:2013-03-15 21:59:53
【问题描述】:

Json.net 具有将对象转换为 json 的异步函数,例如:

json = await JsonConvert.DeserializeObjectAsync<T>

但是当我想将一个对象写入一个 json 文件时,我觉得最好直接使用文件 Stream 来完成。

所以我认为应该是这样的:

 var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite);

    using (IOutputStream outputStream = fileStream.GetOutputStreamAt(0))
    {
        using (StreamWriter sw = new StreamWriter(fileStream.AsStreamForWrite()))
        {
            using (JsonWriter jw = new JsonTextWriter(sw))
            {
                jw.Formatting = Formatting.Indented;

                JsonSerializer serializer = new JsonSerializer();
                serializer.Serialize(jw, obj);
            }
        }

但是在 JsonSerzializer 对象上我找不到异步方法。另外我认为IO操作不应该放在自己的线程中。

推荐的方法是什么?

【问题讨论】:

    标签: c# windows-runtime json.net c#-5.0 winrt-async


    【解决方案1】:

    使用 JSON.NET/Newtonsoft.JSON 无法做到这一点。
    但是,您可以改用 System.Text.Json。
    要获得与 JSON.NET 相同的行为,只需将 IncludeFields 和 PropertyNameCaseInsensitive 设置为 true。

    public static class JsonExtensions
    {
        private static readonly System.Text.Json.JsonSerializerOptions _jsonOptions = 
            new System.Text.Json.JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                IncludeFields = true
            }
        ;
    
    
        public static string ToJson<T>(this T obj)
        {
            return System.Text.Json.JsonSerializer.Serialize<T>(obj, _jsonOptions);
        }
    
    
        public static T FromJson<T>(this string json)
        {
            return System.Text.Json.JsonSerializer.Deserialize<T>(json, _jsonOptions);
        }
    
    
        public static async System.Threading.Tasks.Task ToJsonAsync<T>(this T obj, System.IO.Stream stream)
        {
            await System.Text.Json.JsonSerializer.SerializeAsync(stream, obj, typeof(T), _jsonOptions);
        }
    
    
        public static async System.Threading.Tasks.Task<T> FromJsonAsync<T>(this System.IO.Stream stream)
        {
            return await System.Text.Json.JsonSerializer.DeserializeAsync<T>(stream, _jsonOptions);
        }
    
    }
    

    你去吧。

    另外,如果你想异步序列化为字符串:

    public static async System.Threading.Tasks.Task<string> ToJsonAsync<T>(this T obj)
    {
        string ret = null;
    
        Microsoft.IO.RecyclableMemoryStreamManager streamManager = 
            new Microsoft.IO.RecyclableMemoryStreamManager();
        
        using (System.IO.MemoryStream ms = streamManager.GetStream())
        {
            await System.Text.Json.JsonSerializer.SerializeAsync(ms, obj, typeof(T), _jsonOptions);
            ms.Position = 0;
    
            using (System.IO.TextReader sr = new System.IO.StreamReader(ms, System.Text.Encoding.UTF8))
            {
                ret = await sr.ReadToEndAsync();
            }
    
        }
    
        return ret;
    }
    

    【讨论】:

      【解决方案2】:

      Json.NET 并不真正支持异步反序列化。 JsonConvert 上的异步方法只是对在另一个线程 (which is exactly what a library shouldn't do) 上运行它们的同步方法的包装。

      我认为这里最好的方法是在另一个线程上运行文件访问代码。这不会为您提供async 的全部优势(它会浪费一个线程),但不会阻塞 UI 线程。

      【讨论】:

      • 感谢您指出这一点。我已经想知道如何将字符串操作设为非线程异步。流式处理可以设为异步。也许这将是 Json.net 的一个功能,但显然它还没有实现。所以谢谢你的回答。
      • 这将是一个简单的任务 - 制作 JSON.Net 的异步分支。但问题是,这需要很长时间——有人有大约 80 小时的空闲时间吗?如果不是更多?
      • 同时有一些支持:james.newtonking.com/archive/2017/03/21/… 但是由于 JSON.NET 提供的扩展 API 根本没有为异步做好准备(JsonConverter 等),我认为 fork 不会很有意义。我想一个专门为异步构建的不同序列化库会更有意义。并且异步反序列化需要推式解析器(事件驱动)而不是阻塞式拉式解析器......
      【解决方案3】:

      另见此代码,它使用正确的异步方式(例如,它不会创建大字节数组以避免 LOH 内存分配,它不会等待 IO 操作完成)。

      // create this in the constructor, stream manages can be reused
      // see details in this answer https://stackoverflow.com/a/42599288/185498
      var streamManager = new RecyclableMemoryStreamManager();
      
      using (var file = File.Open(destination, FileMode.Create))
      {
          using (var memoryStream = streamManager.GetStream()) // RecyclableMemoryStream will be returned, it inherits MemoryStream, however prevents data allocation into the LOH
          {
              using (var writer = new StreamWriter(memoryStream))
              {
                  var serializer = JsonSerializer.CreateDefault();
      
                  serializer.Serialize(writer, data);
      
                  await writer.FlushAsync().ConfigureAwait(false);
      
                  memoryStream.Seek(0, SeekOrigin.Begin);
      
                  await memoryStream.CopyToAsync(file).ConfigureAwait(false);
              }
          }
      
          await file.FlushAsync().ConfigureAwait(false);
      }
      

      整个文件:https://github.com/imanushin/AsyncIOComparison/blob/0e2527d5c00c2465e8fd2617ed8bcb1abb529436/IntermediateData/FileNames.cs

      【讨论】:

      • 恕我直言,这个答案比接受的答案要好,因为接受的答案完全忽略了用户试图执行 IO 并且没有提供真实示例的事实。
      • 您假装您的代码“不会创建大字节数组以避免 LOH 内存分配”,但实际上 MemoryStream 在内部就是这样做的,它将所有内容缓存在一个单字节数组中,该数组在需要。
      • @Lucero,绝对同意,我们应该使用 Microsoft.IO.RecyclableMemoryStream,会修复
      • @ManushinIgor 好的,这样更好 - 即使它仍然需要将完整数据缓存在内存中。但请注意,“整个文件”的链接仍然有旧代码。
      • @Lucero,同意稍后在 github 上修复
      猜你喜欢
      • 1970-01-01
      • 2017-06-07
      • 1970-01-01
      • 2012-08-31
      • 1970-01-01
      • 2016-06-07
      • 2011-02-06
      相关资源
      最近更新 更多