Json.net异步写入文件

11

Json.net有异步函数可以将对象转换为JSON,例如:

json = await JsonConvert.DeserializeObjectAsync<T>

但是当我想要将一个对象写入JSON文件时,我认为最好直接使用文件流进行操作。

因此,我认为应该像这样做:

 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);
            }
        }

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

推荐采用什么方法?

3个回答

17
Json.NET并不真正支持异步的序列化和反序列化。在JsonConvert上的异步方法只是将它们包装在另一个线程上运行的同步方法的包装器(这正是一个库不应该做的)。
我认为在这里最好的方法是在另一个线程上运行文件访问代码。这不会给你完全利用async的优势(它会浪费一个线程),但它不会阻塞UI线程。

谢谢你指出这一点。我已经在想一个字符串操作怎么可能是非线程异步的了。 流式处理可以异步进行。也许这对于Json.net来说是一个功能,但显然它还没有被实现。所以感谢你的回答。 - Boas Enkler
这将是一个简单的任务 - 制作JSON.Net的异步分支。然而,问题在于它需要很长时间 - 有人有大约80个小时或更多的空闲时间吗? - Kind Contributor
在此期间有一些支持:http://james.newtonking.com/archive/2017/03/21/json-net-10-0-release-1-async-performance-documentation-and-more 但由于JSON.NET提供的扩展API根本没有为异步准备好(例如JsonConverter等),我认为分支不会有太多意义。我想一个专门为异步构建的不同序列化库会更有意义。而异步反序列化将需要推式解析器(事件驱动)而不是阻塞式拉式解析器... - Lucero
@KindContributor 很多人。你认为这样的库是如何被编写出来的呢?实际上,多年来GitHub上有很多人提供帮助。但作者并没有对这个努力给予足够的支持。现在他对于Json.NET来说基本上已经成了一个幽灵。 - arkon
@arkon 因此我评论说它可以被分叉,这是一个个人可以完成的简单任务。但是现在(我的评论是在2015年),最好是为 .Net Core 做出贡献。他们现在有一个更好的内置 JSON 库(我记得它是基于 Newtonsoft 的) - 也许这就是为什么没有太多志愿者投入原始 newtonsoft 代码库的原因。 - Kind Contributor

9

同时查看这段代码,它使用正确的异步方式(例如不会创建巨大的字节数组以避免 LOH 内存分配,也不会等待 IO 操作完成)。

// create this in the constructor, stream manages can be reused
// see details in this answer https://dev59.com/TnE85IYBdhLWcg3wXCIv#42599288
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

这是一个关于it技术的文件链接,其中包含了有关异步IO比较的信息。请点击以上链接获取更多详细信息。

3
在我看来,考虑到被接受的答案完全忽略了用户正在尝试执行IO并没有提供真实的示例,这个答案比被接受的答案更好。 - James Haug
3
你声称你的代码“不会创建大型字节数组以避免LOH内存分配”,但实际上,MemoryStream 内部就是这样做的,它将所有内容缓存在一个单独的字节数组中,并在需要时自动扩展。 - Lucero
@Lucero,完全同意,我们应该使用Microsoft.IO.RecyclableMemoryStream,我会修复。 - Manushin Igor
3
@ManushinIgor 好的,这样就好多了——即使它仍然需要将完整数据缓存在内存中。但请注意,“整个文件”的链接仍然是旧代码。 - Lucero
@Lucero,同意稍后在 Github 上进行修复。 - Manushin Igor

0

你不能使用 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接