微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 c#.Net 中使用超时进行序列化的更好方法

如何解决在 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. ...

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

解决方法

基于 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,secondsToTimeout)))
                {
                    JsonSerializer.Create(settings).Serialize(jsonWriter,payload);
                    // Log the MaxObservedDepth here,if you want to.
                }
                return writer.ToString();
            }
        }
        catch (Exception)
        {
            disallowedToSerialize.Add(hashEl);
            return "°-°";
        }
    }
}

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

,

运行定时操作的正确方法是执行以下操作。我建议再看看序列化应该如何工作:)

    /// <summary>
    /// Run an action timed.
    /// </summary>
    /// <param name="action">Action to execute timed.</param>
    /// <param name="secondsTimout">Seconds before Task should cancel.</param>
    /// <returns></returns>
    public static async Task RunTimeout(Action action,int secondsTimout) {
        var tokenSource = new CancellationTokenSource();
        tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
        await Task.Run(action,tokenSource.Token);
    }

您可能还想在完成定时任务后返回一个变量。可以这样做...

    public static async Task<T> RunTimeout<T>(Func<T> action,int secondsTimout) {
        var tokenSource = new CancellationTokenSource();
        tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
        var result = await Task.Run(action,tokenSource.Token);
        return result;
    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。