当使用c# BinaryFormatter.Deserialize时,是什么导致UI出现问题?

本文关键字:UI 问题 是什么 BinaryFormatter Deserialize | 更新日期: 2023-09-27 17:50:42

我使用BinaryFormatter来序列化和反序列化条目列表到byte[]数组中,并且我注意到对于列表中的大量元素,我的UI将挂起或有"打嗝"。当我说重要时,我指的是10K个项目(每个项目都有自己的项目集合)。

有趣的是,序列化和反序列化发生在一个单独的线程上,所以我不会认为会发生UI中断。我只是好奇是否有人处理过类似的事情,以及是否有任何解决方法。

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

我也意识到UI线程将被中断以发布状态更新,但这些可以忽略不计。我注意到当BinaryFormatter时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
  }
}

当使用c# BinaryFormatter.Deserialize时,是什么导致UI出现问题?

仅供参考,截至2020年11月,MS建议不要在代码中使用使用BinaryFormatter。

可以考虑使用JsonSerializer或XmlSerializer。有关更多信息,请参见BinaryFormatter安全性指南。