使用C#BinaryFormatter.Deserialize时导致UI打嗝的原因是什么?

时间:2014-03-17 15:18:01

标签: c# serialization binary

我正在使用BinaryFormatter将项目列表序列化和反序列化为byte []数组,我注意到对于列表中的大量元素,我的UI将挂起或出现“打嗝”。当我说重要时,我说的是10K物品(每件物品都有自己的物品)。

有趣的是序列化和反序列化发生在一个单独的线程上,所以我不会想到会发生UI中断。我很好奇是否有人处理了类似的事情,并且有任何变通方法。

下面的代码片段来自我正在测试的剥离的sol'n。虽然我在这里使用BinaryFormatter来读取和写入磁盘,但我更感兴趣的部分是SerializationHelper类中的内容(它在内存中完成)。

我也意识到UI线程会被中断以发布状态更新,但这些可以忽略不计。当BinaryFormatter.Deserialize正在执行并且UI上没有任何更新时,我注意到UI挂起。

的DataReader

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationTesting
{
  internal class DataReader : DataIOBase
  {
    #region Constants

    private const int MAX_STEPS = 2;

    #endregion

    #region Declarations

    private string _file;

    #endregion

    public DataReader(string file)
    {
      _file = file;
    }

    protected override void DoWork()
    {
      UpdateStatus(0, MAX_STEPS, "Reading from file...");

      byte[] serializedData = ReadFromDisk();

      UpdateStatus(1, MAX_STEPS, "Deserializing data...");

      if (serializedData == null) return;

      DeserializeData(serializedData);

      UpdateStatus(2, MAX_STEPS, "Finished!");
    }

    private byte[] ReadFromDisk()
    {
      byte[] serializedData = null;

      using (FileStream stream = new FileStream(_file, FileMode.Open, FileAccess.Read))
      {
        using (BufferedStream bufferedStream = new BufferedStream(stream))
        {
          BinaryFormatter formatter = new BinaryFormatter();
          serializedData = formatter.Deserialize(bufferedStream) as byte[];
        }
      }

      return serializedData;
    }

    private void DeserializeData(byte[] serializedData)
    {
      List<Data> dataList = SerializationHelper.Deserialize(serializedData, new List<Data>());

      dataList.Clear();
      dataList = null;
      serializedData = null;
    }
  }
}

DataWriter

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationTesting
{
  internal class DataWriter : DataIOBase
  {
    #region Declarations

    private string _file;
    private int _count;

    #endregion

    public DataWriter(string file, int count)
    {
      _file = file;
      _count = count;
    }

    protected override void DoWork()
    {
      int maxSteps = _count + 2;

      UpdateStatus(0, maxSteps, "Creating data...");

      List<Data> dataList = CreateData(maxSteps);

      UpdateStatus(_count, maxSteps, "Serializing data...");

      byte[] serializedData = SerializationHelper.Serialize(dataList, null);

      UpdateStatus(_count + 1, maxSteps, "Writing to file...");

      WriteToDisk(serializedData, maxSteps);

      serializedData = null;
      dataList.Clear();
      dataList = null;

      UpdateStatus(maxSteps, maxSteps, "Finished!");
    }

    private List<Data> CreateData(int maxSteps)
    {
      List<Data> dataList = new List<Data>();

      for (int i = 0; i < _count; i++)
      {
        UpdateStatus(i, maxSteps, string.Format("Creating item {0}...", i + 1));

        Data data = new Data();
        dataList.Add(data);
      }

      return dataList;
    }

    private void WriteToDisk(byte[] serializedData, int maxSteps)
    {
      using (FileStream stream = new FileStream(_file, FileMode.Create, FileAccess.Write))
      {
        using (BufferedStream bufferedStream = new BufferedStream(stream))
        {
          BinaryFormatter formatter = new BinaryFormatter();
          formatter.Serialize(bufferedStream, serializedData);
        }
      }
    }
  }
}

DataIOBase

using System.Diagnostics;
using System.Threading;

namespace SerializationTesting
{
  internal abstract class DataIOBase
  {
    #region Declarations

    private Thread _thread;

    #endregion

    #region Events

    public event UpdateStatusHandler OnStatusChange;
    public event ProcessCompleteHandler OnComplete;

    #endregion

    public void Start()
    {
      KillThread();

      _thread = new Thread(new ThreadStart(ThreadBody));
      _thread.Start();
    }

    private void KillThread()
    {
      if (_thread == null) return;
      if (!_thread.IsAlive) return;

      try
      {
        _thread.Abort();
      }
      catch { }
    }

    private void ThreadBody()
    {
      Stopwatch sw = new Stopwatch();
      sw.Start();

      try
      {
        DoWork();
      }
      catch { }
      finally
      {
        sw.Stop();

        if (OnComplete != null)
        {
          OnComplete(sw.ElapsedMilliseconds);
        }
      }
    }

    protected abstract void DoWork();

    protected void UpdateStatus(int curr, int max, string status)
    {
      if (OnStatusChange == null) return;
      OnStatusChange(curr, max, status);
    }
  }
}

数据

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace SerializationTesting
{
  [Serializable]
  public class Data : ISerializable
  {
    #region Static Members

    private static readonly int COLLECTION_SIZE_MIN = 1;
    private static readonly int COLLECTION_SIZE_MAX = 20;

    private static Random _rand = null;
    private static Random Rand
    {
      get
      {
        if (_rand == null)
        {
          _rand = new Random();
        }

        return _rand;
      }
    }

    #endregion

    #region Instance Properties

    public string Name1 { get; set; }
    public string Name2 { get; set; }
    public string Name3 { get; set; }
    public string Name4 { get; set; }
    public string Name5 { get; set; }
    public int Num1 { get; set; }
    public int Num2 { get; set; }
    public int Num3 { get; set; }
    public int Num4 { get; set; }
    public int Num5 { get; set; }
    public bool Bool1 { get; set; }
    public bool Bool2 { get; set; }
    public bool Bool3 { get; set; }
    public bool Bool4 { get; set; }
    public bool Bool5 { get; set; }
    public Dictionary<int, Data> Collection { get; set; }

    #endregion

    public Data(bool createCollection = true)
    {
      Init(createCollection);
    }

    #region Init

    private void Init(bool createCollection)
    {
      try
      {
        Name1 = CreateString();
        Name2 = CreateString();
        Name3 = CreateString();
        Name4 = CreateString();
        Name5 = CreateString();
        Num1 = CreateInt();
        Num2 = CreateInt();
        Num3 = CreateInt();
        Num4 = CreateInt();
        Num5 = CreateInt();
        Bool1 = CreateBool();
        Bool2 = CreateBool();
        Bool3 = CreateBool();
        Bool4 = CreateBool();
        Bool5 = CreateBool();
        CreateCollection(createCollection);
      }
      catch { }
    }

    private string CreateString()
    {
      int length = Rand.Next(1, 31);
      char[] value = new char[length];

      for (int i = 0; i < length; i++)
      {
        int charValue = Rand.Next(48, 91);
        value[i] = (char)i;
      }

      return new string(value);
    }

    private int CreateInt()
    {
      return Rand.Next(1, 11);
    }

    private bool CreateBool()
    {
      int value = Rand.Next(0, 2);

      return value == 0 ? false : true;
    }

    private void CreateCollection(bool populateCollection)
    {
      Collection = new Dictionary<int, Data>();

      if (!populateCollection) return;

      int count = Rand.Next(COLLECTION_SIZE_MIN, COLLECTION_SIZE_MAX + 1);

      for (int i = 0; i < count; i++)
      {
        Data data = new Data(false);
        Collection.Add(i, data);
      }
    }

    #endregion

    #region Serialization

    public Data(SerializationInfo info, StreamingContext context)
    {
      SerializationHelper sh = new SerializationHelper(info);

      Name1 = sh.GetItem("Name1", string.Empty);
      Name2 = sh.GetItem("Name2", string.Empty);
      Name3 = sh.GetItem("Name3", string.Empty);
      Name4 = sh.GetItem("Name4", string.Empty);
      Name5 = sh.GetItem("Name5", string.Empty);
      Num1 = sh.GetItem("Num1", -1);
      Num2 = sh.GetItem("Num2", -1);
      Num3 = sh.GetItem("Num3", -1);
      Num4 = sh.GetItem("Num4", -1);
      Num5 = sh.GetItem("Num5", -1);
      Bool1 = sh.GetItem("Bool1", false);
      Bool2 = sh.GetItem("Bool2", false);
      Bool3 = sh.GetItem("Bool3", false);
      Bool4 = sh.GetItem("Bool4", false);
      Bool5 = sh.GetItem("Bool5", false);
      Collection = sh.GetItem("Collection", new Dictionary<int, Data>());
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      info.AddValue("Name1", Name1);
      info.AddValue("Name2", Name2);
      info.AddValue("Name3", Name3);
      info.AddValue("Name4", Name4);
      info.AddValue("Name5", Name5);
      info.AddValue("Num1", Num1);
      info.AddValue("Num2", Num2);
      info.AddValue("Num3", Num3);
      info.AddValue("Num4", Num4);
      info.AddValue("Num5", Num5);
      info.AddValue("Bool1", Bool1);
      info.AddValue("Bool2", Bool2);
      info.AddValue("Bool3", Bool3);
      info.AddValue("Bool4", Bool4);
      info.AddValue("Bool5", Bool5);
      info.AddValue("Collection", Collection);
    }

    #endregion
  }
}

SerializationHelper

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationTesting
{
  public class SerializationHelper
  {
    #region Declarations

    private SerializationInfo _serializationInfo;

    #endregion

    public SerializationHelper(SerializationInfo serializationInfo)
    {
      _serializationInfo = serializationInfo;
    }

    #region Get Item

    public T GetItem<T>(string item, T defaultValue)
    {
      try
      {
        object value = _serializationInfo.GetValue(item, typeof(T));
        return (T)value;
      }
      catch
      {
        return defaultValue;
      }
    }

    #endregion

    #region Binary Serialization

    public static T Deserialize<T>(byte[] serializedObject, T defaultValue, SerializationBinder binder = null)
    {
      if (serializedObject == null) return defaultValue;

      try
      {
        object deserializedObject;
        BinaryFormatter binaryFormatter = new BinaryFormatter();

        if (binder != null)
        {
          binaryFormatter.Binder = binder;
        }

        using (MemoryStream stream = new MemoryStream(serializedObject))
       {
          stream.Seek(0, 0);

          deserializedObject = binaryFormatter.Deserialize(stream);
        }

        if (!(deserializedObject is T))
        {
          return defaultValue;
        }

        return (T)deserializedObject;
      }
      catch
      {
        return defaultValue;
      }
    }

    public static byte[] Serialize(object o, byte[] defaultValue, SerializationBinder binder = null)
    {
      if (o == null) return null;

      try
      {
        byte[] serializedObject;
        BinaryFormatter binaryFormatter = new BinaryFormatter();

        if (binder != null)
        {
          binaryFormatter.Binder = binder;
        }

        using (MemoryStream stream = new MemoryStream())
        {
          binaryFormatter.Serialize(stream, o);
          serializedObject = stream.ToArray();
        }

        return serializedObject;
      }
      catch
      {
        return defaultValue;
      }
    }

    #endregion
  }
}

1 个答案:

答案 0 :(得分:0)

仅供参考,截至 2020 年 11 月,MS 建议在您的代码中使用 not using using BinaryFormatter

相反,请考虑使用 JsonSerializer 或 XmlSerializer。有关详细信息,请参阅 BinaryFormatter security guide