首页 > 解决方案 > 在 c#.Net 中使用超时进行序列化的更好方法

问题描述

我的用例:在单线程应用程序中,我需要序列化任意类以进行日志记录。

任意类主要以自动方式从大型 VB6 应用程序转换为 .NET。

如果在没有超时的情况下进行序列化,则序列化方法将循环直到内存不足。

这是我目前拥有的:

internal class Serializer
{
    private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public volatile string result = null;
    public volatile Func<string> toExecute = null;
    public Thread thread;
    public ManualResetEventSlim messageToSender = new ManualResetEventSlim(false);
    public ManualResetEventSlim messageToReceiver = new ManualResetEventSlim(false);


    public Serializer()
    {
        thread = new Thread(new ThreadStart(run));
        thread.Start();
    }
    ~Serializer()
    {
        try
        {
            if (messageToSender != null) messageToSender.Dispose();
        }
        catch { };
        try
        {
            if (messageToReceiver != null) messageToReceiver.Dispose();
        }
        catch { };
    }

    public volatile bool ending = false;
    public void run()
    {
        while (!ending)
        {
            try
            {
                if (toExecute != null)
                {
                    result = toExecute();
                }
                messageToReceiver.Reset();
                messageToSender.Set();
                messageToReceiver.Wait();
            }
            catch (ThreadInterruptedException)
            {
                log.Warn("Serialization interrupted");
                break;
            }
            catch (ThreadAbortException)
            {
                Thread.ResetAbort();
                result = null;
            }
            catch (Exception ex)
            {
                log.Error("Error in Serialization", ex);
                Console.WriteLine(ex);
                break;
            }
        }
    }
}
public class LocalStructuredLogging
{
    private static volatile Serializer _serializer;
    private static Serializer serializer
    {
        get
        {
            if (_serializer == null)
            {
                _serializer = new Serializer();
            }
            return _serializer;
        }
    }

    public void LogStucturedEnd()
    {
        try
        {
            if (serializer != null)
            {
                serializer.ending = true;
                serializer.thread.Interrupt();
            }
        }
        catch { }
    }
    internal ConcurrentDictionary<long, bool> disallowedToSerialize = new ConcurrentDictionary<long, bool>();
    public string TrySerialize<T>(T payload, [CallerLineNumber] int line = 0)
    {
        long hashEl = typeof(T).Name.GetHashCode() * line;

        bool dummy;
        unchecked
        {
            if (disallowedToSerialize.TryGetValue(hashEl, out dummy))
            {
                return "°,°";
            }
        }

        serializer.toExecute = () =>
        {
            try
            {
                return Newtonsoft.Json.JsonConvert.SerializeObject(payload, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
            }
            catch (Exception)
            {
                disallowedToSerialize.TryAdd(hashEl, false);
                return "°°°";
            }
        };

        try
        {
            serializer.messageToSender.Reset();
            serializer.messageToReceiver.Set();

            if (serializer.messageToSender.Wait(6000))
            {
                return Interlocked.Exchange(ref serializer.result, null);
            }

            serializer.toExecute = null;
            serializer.thread.Abort();
            serializer.messageToSender.Wait(2000);

            disallowedToSerialize.TryAdd(hashEl, false);
            return "°§°";
        }
        catch (Exception)
        {
            disallowedToSerialize.TryAdd(hashEl, false);
            return "°-°";
        }
    }
}

代码调用如下(test是任意类实例):

var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);

尽管它似乎可以完成这项工作,但它存在一些问题:

  1. 它依赖于 Thread.Abort
  2. 它是时间相关的,因此它会在加载的系统上产生不同的结果
  3. 每个类实例都像其他所有类实例一样对待 - 无需调整
  4. ...

那么,有没有更好的解决方案?

标签: c#.netserializationjson.nettimeout

解决方案


基于 dbc 的出色回答,我设法创建了一个更好的定时序列化程序。它解决了上面提到的所有 3 个问题:

public class TimedJsonTextWriter : JsonTextWriter
{
    public int? MaxDepth { get; set; }
    public TimeSpan? MaxTimeUsed { get; set; }
    public int MaxObservedDepth { get; private set; }

    private DateTime start = DateTime.Now;

    public TimedJsonTextWriter(TextWriter writer, JsonSerializerSettings settings, TimeSpan? maxTimeUsed)
        : base(writer)
    {
        this.MaxDepth = (settings == null ? null : settings.MaxDepth);
        this.MaxObservedDepth = 0;
        this.MaxTimeUsed = maxTimeUsed;
    }

    public TimedJsonTextWriter(TextWriter writer, TimeSpan? maxTimeUsed, int? maxDepth = null)
        : base(writer)
    {
        this.MaxDepth = maxDepth;
        this.MaxTimeUsed = maxTimeUsed;
    }

    public override void WriteStartArray()
    {
        base.WriteStartArray();
        CheckDepth();
    }

    public override void WriteStartConstructor(string name)
    {
        base.WriteStartConstructor(name);
        CheckDepth();
    }

    public override void WriteStartObject()
    {
        base.WriteStartObject();
        CheckDepth();
    }

    uint checkDepthCounter = 0;
    private void CheckDepth()
    {
        MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
        if (Top > MaxDepth)
            throw new JsonSerializationException($"Depth {Top} Exceeds MaxDepth {MaxDepth} at path \"{Path}\"");
        unchecked
        {
            if ((++checkDepthCounter & 0x3ff) == 0 && DateTime.Now - start > MaxTimeUsed)
                throw new JsonSerializationException($"Time Usage Exceeded at path \"{Path}\"");
        }
    }
}


public class LocalStructuredLogging
{
    public void LogStucturedEnd()
    {
    }

    internal HashSet<long> disallowedToSerialize = new HashSet<long>();
    public string TrySerialize<T>(T payload, int maxDepth = 100, int secondsToTimeout = 2, [CallerLineNumber] int line = 0)
    {
        long hashEl = typeof(T).Name.GetHashCode() * line;

        if (disallowedToSerialize.Contains(hashEl))
        {
            return "°,°";
        }

        try
        {
            var settings = new JsonSerializerSettings { MaxDepth = maxDepth, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore };
            using (var writer = new StringWriter())
            {
                using (var jsonWriter = new TimedJsonTextWriter(writer, settings, new TimeSpan(0, 0, secondsToTimeout)))
                {
                    JsonSerializer.Create(settings).Serialize(jsonWriter, payload);
                    // Log the MaxObservedDepth here, if you want to.
                }
                return writer.ToString();
            }
        }
        catch (Exception)
        {
            disallowedToSerialize.Add(hashEl);
            return "°-°";
        }
    }
}

剩下的唯一问题是哈希冲突,这很容易解决(例如,同时使用源文件名或使用其他类型的集合)。


推荐阅读