问题描述
我的用例: 在单线程应用程序中,我需要序列化任意类以进行日志记录。
任意类主要以自动方式从庞大的 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 "°-°";
}
}
}
var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);
虽然它似乎可以完成工作,但它存在一些问题:
那么,有没有更好的解决方案?
解决方法
基于 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;
}