【问题标题】:Serializing a ConcurrentBag of XAML序列化 XAML 的 ConcurrentBag
【发布时间】:2013-12-27 00:03:15
【问题描述】:

我的代码中有一个ConcurrentBag<Point3DCollection>

我正在尝试弄清楚如何对它们进行序列化。当然,我可以使用提供者模型类对其进行迭代或打包,但我想知道它是否已经完成。

Point3DCollections 本身可能非常大,可以进行压缩以加快读写磁盘的速度,但我需要的响应时间主要是在用户界面范围内。换句话说,出于性能原因,我更喜欢二进制格式而不是 XAML 文本格式。 (有一个很好的 XAML 文本序列化程序,它是 Helix 3D CodeProject 的一部分,但它比我想要的要慢。)

这是一个我要推出自己的序列化程序的用例,还是已经为这种数据打包了一些东西?

【问题讨论】:

  • 我认为这很有争议。 Point3D 是 3 个双精度值。 double 的长度为 8 字节,因此二进制序列化的 Point3D 为 24 字节。 XAML 中使用的由人类(甚至是 Blend)创建的许多双精度值可以比序列化为字符串的值更短(我假设您会将它们写为 ANSI,而不是 UNICODE)。如果不是这种情况,则意味着压缩将毫无用处,因为在这种情况下,二进制序列化双精度值列表的熵会很高。我建议你简单地遍历包,并使用开箱即用的 Point3DCollectionConverter 类(ConvertTo 方法)。
  • @SimonMourier,这些值通常由 CAD 系统生成,因此双精度 ANSI 字符串会比 8 个字节长得多。但是您对 Point3DCollectionConverter 的赞扬是一个可能的答案。您是否愿意将其写为答案,允许我进行投票等?
  • 对于大量的数据,为什么不考虑Sqlite等,它可以将结构化数据存储在文件中。我见过许多使用数据库来存储结构和关系的 3d 程序,这允许它们部分插入/更新/删除数据。 Sqlite 的好处是,您可以使用多线程序列化来提高速度,但是您需要在 sqlite 上做一些工作以启用多线程 sqlite 连接,或者您可以使用 SQL Express 的 LocalDB 甚至 Sql Compact。
  • @AkashKava,请写下这个答案! :-) 我想进一步研究一下。

标签: c# .net wpf xaml concurrency


【解决方案1】:

这里有一些处理Point3DCollection包的字符串和二进制序列化的扩展方法。正如我在评论中所说,我不认为在所有情况下都有最好的方法,所以你可能想同时尝试。另请注意,它们使用 Stream 参数作为输入,因此您可以通过调用 GZipStreamDeflateStream 来链接这些参数。

public static class Point3DExtensions
{
    public static void StringSerialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        StreamWriter writer = new StreamWriter(stream);
        Point3DCollectionConverter converter = new Point3DCollectionConverter();
        foreach (Point3DCollection coll in bag)
        {
            // we need to use the english locale as the converter needs that for parsing...
            string line = (string)converter.ConvertTo(null, CultureInfo.GetCultureInfo("en-US"), coll, typeof(string));
            writer.WriteLine(line);
        }
        writer.Flush();
    }

    public static void StringDeserialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        StreamReader reader = new StreamReader(stream);
        Point3DCollectionConverter converter = new Point3DCollectionConverter();
        do
        {
            string line = reader.ReadLine();
            if (line == null)
                break;

            bag.Add((Point3DCollection)converter.ConvertFrom(line));

            // NOTE: could also use this:
            //bag.Add(Point3DCollection.Parse(line));
        }
        while (true);
    }

    public static void BinarySerialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        BinaryWriter writer = new BinaryWriter(stream);
        writer.Write(bag.Count);
        foreach (Point3DCollection coll in bag)
        {
            writer.Write(coll.Count);
            foreach (Point3D point in coll)
            {
                writer.Write(point.X);
                writer.Write(point.Y);
                writer.Write(point.Z);
            }
        }
        writer.Flush();
    }

    public static void BinaryDeserialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        BinaryReader reader = new BinaryReader(stream);
        int count = reader.ReadInt32();
        for (int i = 0; i < count; i++)
        {
            int pointCount = reader.ReadInt32();
            Point3DCollection coll = new Point3DCollection(pointCount);
            for (int j = 0; j < pointCount; j++)
            {
                coll.Add(new Point3D(reader.ReadDouble(), reader.ReadDouble(), reader.ReadDouble()));
            }
            bag.Add(coll);
        }
    }
}

还有一个可以玩的小控制台应用测试程序:

    static void Main(string[] args)
    {
        Random rand = new Random(Environment.TickCount);
        ConcurrentBag<Point3DCollection> bag = new ConcurrentBag<Point3DCollection>();
        for (int i = 0; i < 100; i++)
        {
            Point3DCollection coll = new Point3DCollection();
            bag.Add(coll);

            for (int j = rand.Next(10); j < rand.Next(100); j++)
            {
                Point3D point = new Point3D(rand.NextDouble(), rand.NextDouble(), rand.NextDouble());
                coll.Add(point);
            }
        }

        using (FileStream stream = new FileStream("test.bin", FileMode.Create))
        {
            bag.StringSerialize(stream); // or Binary
        }

        ConcurrentBag<Point3DCollection> newbag = new ConcurrentBag<Point3DCollection>();
        using (FileStream stream = new FileStream("test.bin", FileMode.Open))
        {
            newbag.StringDeserialize(stream); // or Binary
            foreach (Point3DCollection coll in newbag)
            {
                foreach (Point3D point in coll)
                {
                    Console.WriteLine(point);
                }
                Console.WriteLine();
            }
        }
    }
}

【讨论】:

  • 作为一个信息点,对于我的用例,我正在对二进制 gzip 流进行 3 比 1 压缩。我还没有测试过测试 gzipped 方法。
【解决方案2】:

压缩可能会利用重复坐标。序列化程序也经常使用重复对象的引用,尽管我不确定是否有很多设置可以使用结构(如 Point3D)。无论如何,这里有一些如何序列化的例子。要使用标准格式化程序,您需要将数据类型转换为它们大多数支持的类型:列表/数组。下面的代码使用了 Nuget 包 NUnit 和 Json.NET。

using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using NUnit.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Media.Media3D;

namespace DemoPoint3DSerialize
{
    [TestFixture]
    class Tests
    {
        [Test]
        public void DemoBinary()
        {
            // this shows how to convert them all to strings
            var collection = CreateCollection();
            var data = collection.Select(c => c.ToArray()).ToList(); // switch to serializable types
            var formatter = new BinaryFormatter();

            using (var ms = new MemoryStream())
            {
                formatter.Serialize(ms, data);
                Trace.WriteLine("Binary of Array Size: " + ms.Position);
                ms.Position = 0;
                var dupe = (List<Point3D[]>)formatter.Deserialize(ms);
                var result = new ConcurrentBag<Point3DCollection>(dupe.Select(r => new Point3DCollection(r)));
                VerifyEquality(collection, result);
            }
        }

        [Test]
        public void DemoString()
        {
            // this shows how to convert them all to strings
            var collection = CreateCollection();
            IEnumerable<IList<Point3D>> tmp = collection;
            var strings = collection.Select(c => c.ToString()).ToList();

            Trace.WriteLine("String Size: " + strings.Sum(s => s.Length)); // eh, 2x for Unicode
            var result = new ConcurrentBag<Point3DCollection>(strings.Select(r => Point3DCollection.Parse(r)));

            VerifyEquality(collection, result);
        }

        [Test]
        public void DemoDeflateString()
        {
            // this shows how to convert them all to strings
            var collection = CreateCollection();
            var formatter = new BinaryFormatter(); // not really helping much: could 
            var strings = collection.Select(c => c.ToString()).ToList();

            using (var ms = new MemoryStream())
            {
                using (var def = new DeflateStream(ms, CompressionLevel.Optimal, true))
                {
                    formatter.Serialize(def, strings);
                }
                Trace.WriteLine("Deflate Size: " + ms.Position);
                ms.Position = 0;
                using (var def = new DeflateStream(ms, CompressionMode.Decompress))
                {
                    var stringsDupe = (IList<string>)formatter.Deserialize(def);
                    var result = new ConcurrentBag<Point3DCollection>(stringsDupe.Select(r => Point3DCollection.Parse(r)));

                    VerifyEquality(collection, result);
                }
            }
        }

        [Test]
        public void DemoStraightJson()
        {
            // this uses Json.NET
            var collection = CreateCollection();
            var formatter = new JsonSerializer();

            using (var ms = new MemoryStream())
            {
                using (var stream = new StreamWriter(ms, new UTF8Encoding(true), 2048, true))
                using (var writer = new JsonTextWriter(stream))
                {
                    formatter.Serialize(writer, collection);
                }
                Trace.WriteLine("JSON Size: " + ms.Position);
                ms.Position = 0;
                using (var stream = new StreamReader(ms))
                using (var reader = new JsonTextReader(stream))
                {
                    var result = formatter.Deserialize<List<Point3DCollection>>(reader);
                    VerifyEquality(collection, new ConcurrentBag<Point3DCollection>(result));
                }
            }
        }

        [Test]
        public void DemoBsonOfArray()
        {
            // this uses Json.NET
            var collection = CreateCollection();
            var formatter = new JsonSerializer();

            using (var ms = new MemoryStream())
            {
                using (var stream = new BinaryWriter(ms, new UTF8Encoding(true), true))
                using (var writer = new BsonWriter(stream))
                {
                    formatter.Serialize(writer, collection);
                }
                Trace.WriteLine("BSON Size: " + ms.Position);
                ms.Position = 0;
                using (var stream = new BinaryReader(ms))
                using (var reader = new BsonReader(stream, true, DateTimeKind.Unspecified))
                {
                    var result = formatter.Deserialize<List<Point3DCollection>>(reader); // doesn't seem to read out that concurrentBag
                    VerifyEquality(collection, new ConcurrentBag<Point3DCollection>(result));
                }
            }
        }

        private ConcurrentBag<Point3DCollection> CreateCollection()
        {
            var rand = new Random(42);
            var bag = new ConcurrentBag<Point3DCollection>();

            for (int i = 0; i < 10; i++)
            {
                var collection = new Point3DCollection();
                for (int j = 0; j < i + 10; j++)
                {
                    var point = new Point3D(rand.NextDouble(), rand.NextDouble(), rand.NextDouble());
                    collection.Add(point);
                }
                bag.Add(collection);
            }
            return bag;
        }

        private class CollectionComparer : IEqualityComparer<Point3DCollection>
        {
            public bool Equals(Point3DCollection x, Point3DCollection y)
            {
                return x.SequenceEqual(y);
            }

            public int GetHashCode(Point3DCollection obj)
            {
                return obj.GetHashCode();
            }
        }

        private void VerifyEquality(ConcurrentBag<Point3DCollection> collection, ConcurrentBag<Point3DCollection> result)
        {
            var first = collection.OrderBy(c => c.Count);
            var second = collection.OrderBy(c => c.Count);
            first.SequenceEqual(second, new CollectionComparer());
        }


    }
}

【讨论】:

    【解决方案3】:

    使用 Google 的 protobuf-net。 protobuf-net 是 Google 协议缓冲区二进制序列化格式的开源 .net 实现,可用作 BinaryFormatter 序列化程序的替代品。这可能是最快且最容易实施的解决方案。

    这里是 protobuf-net 的主要 google wiki 的链接。在左侧,您会找到所有最新二进制文件的下载。

    https://code.google.com/p/protobuf-net/

    这是一篇很棒的文章,您可能想先看看它以了解它的工作原理。

    http://wallaceturner.com/serialization-with-protobuf-net

    这里是 google wiki 上关于您的具体问题的讨论的链接。答案在页面底部。这就是我得到以下代码并用您帖子中的详细信息替换的地方。

    https://code.google.com/p/protobuf-net/issues/detail?id=354

    我自己没有使用过它,但它看起来是满足您所陈述需求的一个很好的解决方案。根据我收集到的信息,您的代码最终会对此有所不同。

    [ProtoContract]
    public class MyClass {
        public ConcurrentQueue<Point3DCollection> Points {get;set;}
    
        [ProtoMember(1)]
        private Point3DCollection[] Items
        {
            get { return Points.ToArray(); }
            set { Items = new ConcurrentBag<Point3DCollection>(value); }
        }
    }
    

    祝你好运。保重。

    【讨论】:

      【解决方案4】:

      对于大量的数据,你为什么不考虑使用Sqlite或其他任何可以将结构化数据存储在文件中的小型数据库系统等。

      我见过许多 3d 程序使用数据库来存储结构和关系,这允许它们部分插入/更新/删除数据。

      Sqlite/数据库的好处是多线程序列化以提高速度,但是您需要在 sqlite 上做一些工作以启用多线程 sqlite 连接,否则您可以使用 SQL Express 的 LocalDB 甚至 Sql Compact。

      加载数据的一些工作量也可以通过查询来完成,这将被数据库很好地索引。而且大部分事情都可以在后台工作人员上完成,而不会干扰用户界面。

      Sqlite对多线程的支持有限,可以在这里探索http://www.sqlite.org/threadsafe.html

      Sql Compact 是线程安全的,需要安装,无需管理员权限即可安装。你也可以使用实体框架。

      【讨论】:

      • 是否可以添加一个链接来描述通过 sqlite 进行多线程所需的工作?
      猜你喜欢
      • 2023-03-19
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 2020-10-29
      • 1970-01-01
      • 2018-01-07
      • 1970-01-01
      • 2011-02-26
      相关资源
      最近更新 更多