Сериализация ConcurrentBag XAML

В моем коде есть ConcurrentBag<Point3DCollection>.

Я пытаюсь выяснить, как их сериализовать. Конечно, я мог бы перебирать или упаковывать его с помощью класса модели поставщика, но мне интересно, было ли это уже сделано.

Point3DCollection сами потенциально довольно большие и могут быть сжаты для ускорения чтения и записи на диск и с диска, но время отклика, которое мне нужно для этого, в значительной степени зависит от масштаба пользовательского интерфейса. Другими словами, я предпочитает двоичное форматирование по форматированию XAML-текста по соображениям производительности. (Существует хороший сериализатор XAML-текста, который является частью Helix 3D CodeProject, но он медленнее, чем мне хотелось бы.)

Является ли это прецедентом, когда я оставляю свой собственный сериализатор, или есть что-то там, уже упакованное для такого рода данных?

Ответ 1

Вот несколько методов расширения, которые обрабатывают строчную и двоичную сериализацию пакетов Point3DCollection. Как я уже сказал в своем комментарии, я не думаю, что во всех случаях это лучший способ, поэтому вы можете попробовать оба. Также обратите внимание, что они используют параметр Stream в качестве входных данных, поэтому вы можете связать их с вызовами GZipStream DeflateStream.

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

Ответ 2

Сжатие потенциально может использовать повторяющиеся координаты. Сериализаторы часто используют ссылки для повторяющихся объектов, хотя я не уверен, что есть много возможностей для работы с структурами (например, Point3D). Во всяком случае, вот несколько примеров того, как сериализовать это. Чтобы использовать стандартные форматы, вам необходимо преобразовать тип данных в то, что большинство из них поддерживает: list/array. В приведенном ниже коде используются пакеты 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 - это версия с открытым исходным кодом .net для бинарного формата сериализации буфера протокола Google, который может использоваться в качестве замены для сериализатора BinaryFormatter. Вероятно, это будет самое быстрое решение и проще всего реализовать.

Вот ссылка на основную вики google для protobuf-net. Слева вы найдете загрузки для всех самых обновленных бинарных файлов.

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

Вот отличная статья, на которой вы можете сначала взглянуть, как это работает.

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

Вот ссылка на обсуждение вики Google о вашей конкретной проблеме. Ответ находится в нижней части страницы. Это, где я получил код ниже и заменял детали из вашего сообщения.

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/database будет многопоточной сериализацией для повышения скорости, однако вам нужно немного поработать над sqlite, чтобы включить многопоточное SQL-соединение, иначе вы можете использовать LocalDB SQL Express или даже Sql Compact.

Кроме того, некоторые из загрузок данных загрузки могут быть сделаны с помощью запросов, которые будут хорошо индексироваться по базе данных. И многое может быть сделано на фоне рабочего, не мешая пользовательскому интерфейсу.

Sqlite имеет ограниченную поддержку нескольких потоков, которую можно изучить здесь http://www.sqlite.org/threadsafe.html

Sql Compact является потокобезопасным и требует установки, которая может быть установлена ​​без прав администратора. И вы также можете использовать инфраструктуру Entity.