从 BackgroundWorker 转向 TPL 进行日志记录 class
Getting away from BackgroundWorker towards TPL for a logging class
我目前在老Backgroundworker
class的观点下写了一个简单的事件记录器。我正在尝试将其转换为 TPL 实现。
我对 C# 中的线程没有足够的使用,所以我更喜欢其中一个,但我知道 TPL 越来越受欢迎,我想尽可能多地坚持使用它。另一个原因是,使用当前代码,我找不到使 EventLog
class 线程安全的简单方法。我发现自己使用 BeginInvoke
从非 UI 线程写入日志,这对我来说似乎很乱。
所以这是原始代码。
public class EventLog
{
public String LogPath { get; set; }
public List<LogEvent> Events { get; private set; }
public static EventLog Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<EventLog> lazyInstance = new Lazy<EventLog>(() => new EventLog());
private EventLog()
{
Events = new List<LogEvent>();
LogPath = Assembly.GetExecutingAssembly().CodeBase;
LogPath = Path.GetDirectoryName(LogPath);
LogPath = LogPath.Replace("file:\", "");
LogPath = LogPath + "\Log.txt";
}
public override void publish(LogEvent newEvent)
{
Events.Add(newEvent);
if (!LogEventWriter.Instance.IsBusy)
LogEventWriter.Instance.RunWorkerAsync(LogPath);
LogEventWriter.Instance.LogEvents.Add(newEvent);
}
}
internal class LogEventWriter : BackgroundWorker
{
public BlockingCollection<LogEvent> LogEvents { get; set; }
public static LogEventWriter Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<LogEventWriter> lazyInstance = new Lazy<LogEventWriter>(() => new LogEventWriter());
private LogEventWriter()
{
WorkerSupportsCancellation = true;
LogEvents = new BlockingCollection<LogEvent>();
}
protected override void OnDoWork(DoWorkEventArgs e)
{
if (e.Argument != null && e.Argument is String)
{
String logPath = (String)e.Argument;
using (StreamWriter logFile = new StreamWriter(logPath, true))
{
while (!CancellationPending)
{
LogEvent anEvent = LogEvents.Take();
logFile.WriteLine(anEvent.Message);
logFile.Flush();
if (anEvent.Message.Contains("Application Terminated"))
break;
}
logFile.Close();
}
}
e.Cancel = true;
}
}
我目前对日志的思路是在系统出现故障时尽快将日志写入文件,这样日志就会有尽可能多的信息。这就是 Backgroundworker
的用途。我还在 EventLog
class 中保留了一个 List<LogEvent>
,这样用户就可以在当前日志中搜索特定事件(未完全关闭 implemented/polished)。
这是我目前的 TPL 解决方案。我尽我所能将日志记录功能包装到 Task
s 中,但我仍然觉得我应该有一个类似于 publish
的功能,而不必直接将 LogEvent
s 放入a BlockingCollection<>
这样我就可以 运行 在与主 UI.
不同的线程上进行日志记录
还有没有一种更简洁的方法可以停止 Task
,而不必从它们的循环中向 break
发送 "special" LogEvent
?
public class EventLog
{
public static EventLog Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<EventLog> lazyInstance = new Lazy<EventLog>(() => new EventLog());
public String LogPath { get; set; }
public ConcurrentQueue<LogEvent> Events { get; set; }
private EventLog()
{
Events = new ConcurrentQueue<LogEvent>();
WriteQueue = new BlockingCollection<LogEvent>();
LogEventQueue = new BlockingCollection<LogEvent>();
LogPath = Assembly.GetExecutingAssembly().CodeBase;
LogPath = Path.GetDirectoryName(LogPath);
LogPath = LogPath.Replace("file:\", "");
LogPath = LogPath + "\LogASDF.txt";
StartManager();
StartWriter();
}
public BlockingCollection<LogEvent> LogEventQueue { get; set; }
private void StartManager()
{
var writeTask = Task.Factory.StartNew(() =>
{
while (true)
{
LogEvent anEvent = LogEventQueue.Take();
Events.Enqueue(anEvent);
WriteQueue.Add(anEvent);
if (anEvent.Message.Contains("Application Terminated"))
break;
}
});
}
private BlockingCollection<LogEvent> WriteQueue { get; set; }
private void StartWriter()
{
var writeTask = Task.Factory.StartNew(() =>
{
using (StreamWriter logFile = new StreamWriter(LogPath, true))
{
while(true)
{
LogEvent anEvent = WriteQueue.Take();
logFile.WriteLine(anEvent.Message);
logFile.Flush();
if (anEvent.Message.Contains("Application Terminated"))
break;
}
logFile.Close();
}
});
}
}
- 如何正确使用
CancellationToken
来取消这两个任务?我看不出 BlockingCollection
是如何阻塞的,我总是必须 "pulse" 集合才能解除阻塞。
- 有没有一种 "cleaner" 方法可以将
LogEvent
插入到日志中而不必直接将其插入到 LogEventQueue
中?
现在你的代码不是 thread-safe
因为你有这个:
public List<LogEvent> Events { get; private set; }
List<T>
不是线程安全的,可以从外部代码更改。而且我根本看不出它是否被使用。
另外,你真的应该在你的代码中使用 CancellationToken
因为在其他情况下你会遇到麻烦:例如,你有 5 条消息在队列中,你决定取消你的工作。在这种情况下,检查 Shutdown Log
只会在一段时间后中断循环,这会让 class.
的最终用户感到困惑
此外,BlockingCollection<T>.Take
方法有一个重载 CancellationToken
,但如果取消,您将获得 OperationCanceledException
:
try
{
LogEvent anEvent = WriteQueue.Take(CancellationPending);
}
catch (OperationCanceledException ex)
{
// handle stop here;
}
死循环在多线程中非常糟糕的做法,建议不要使用。
以下是我使用 .net 4.5 处理此问题的方法。对事件队列的所有访问都是同步的,因此不需要锁定或同步:
public class EventLog
{
public String LogPath { get; set; }
public List<LogEvent> Events {get;set;}
private isProcessing = false;
public CancellationTokenSource cts = new CancellationTokenSource();
private CancellationToken _token;
public static EventLog Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<EventLog> lazyInstance = new Lazy<EventLog>(() => new EventLog());
private EventLog()
{
Events = new List<LogEvent>();
Events.CollectionChanged += Events_CollectionChanged;
LogPath = Assembly.GetExecutingAssembly().CodeBase;
LogPath = Path.GetDirectoryName(LogPath);
LogPath = LogPath.Replace("file:\", "");
LogPath = LogPath + "\Log.txt";
_token = cts.Token;
}
public override void publish(LogEvent newEvent)
{
Events.Add(newEvent);
if (!isProcessing)
ProcessLog();
}
private async void ProcessLog()
{
while (Events.Count > 0)
{
isProcessing = true;
LogEvent e = EventLogs.First();
await Task.Run (() => { WriteLog(e,token); },_token);
EventLogs.Remove(e);
if (_token.IsCancellationRequested == true)
EventLogs.Clear();
}
isProcessing = false;
}
private void WriteLog(LogEvent e,CancellationToken token)
{
using (StreamWriter logFile = new StreamWriter(LogPath, true))
{
if (token.IsCancellationRequested == false)
{
logFile.WriteLine(e.Message);
logFile.Flush();
}
}
}
}
编辑:添加了取消标记。
编辑 2:添加了 WriteLog 函数。
我目前在老Backgroundworker
class的观点下写了一个简单的事件记录器。我正在尝试将其转换为 TPL 实现。
我对 C# 中的线程没有足够的使用,所以我更喜欢其中一个,但我知道 TPL 越来越受欢迎,我想尽可能多地坚持使用它。另一个原因是,使用当前代码,我找不到使 EventLog
class 线程安全的简单方法。我发现自己使用 BeginInvoke
从非 UI 线程写入日志,这对我来说似乎很乱。
所以这是原始代码。
public class EventLog
{
public String LogPath { get; set; }
public List<LogEvent> Events { get; private set; }
public static EventLog Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<EventLog> lazyInstance = new Lazy<EventLog>(() => new EventLog());
private EventLog()
{
Events = new List<LogEvent>();
LogPath = Assembly.GetExecutingAssembly().CodeBase;
LogPath = Path.GetDirectoryName(LogPath);
LogPath = LogPath.Replace("file:\", "");
LogPath = LogPath + "\Log.txt";
}
public override void publish(LogEvent newEvent)
{
Events.Add(newEvent);
if (!LogEventWriter.Instance.IsBusy)
LogEventWriter.Instance.RunWorkerAsync(LogPath);
LogEventWriter.Instance.LogEvents.Add(newEvent);
}
}
internal class LogEventWriter : BackgroundWorker
{
public BlockingCollection<LogEvent> LogEvents { get; set; }
public static LogEventWriter Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<LogEventWriter> lazyInstance = new Lazy<LogEventWriter>(() => new LogEventWriter());
private LogEventWriter()
{
WorkerSupportsCancellation = true;
LogEvents = new BlockingCollection<LogEvent>();
}
protected override void OnDoWork(DoWorkEventArgs e)
{
if (e.Argument != null && e.Argument is String)
{
String logPath = (String)e.Argument;
using (StreamWriter logFile = new StreamWriter(logPath, true))
{
while (!CancellationPending)
{
LogEvent anEvent = LogEvents.Take();
logFile.WriteLine(anEvent.Message);
logFile.Flush();
if (anEvent.Message.Contains("Application Terminated"))
break;
}
logFile.Close();
}
}
e.Cancel = true;
}
}
我目前对日志的思路是在系统出现故障时尽快将日志写入文件,这样日志就会有尽可能多的信息。这就是 Backgroundworker
的用途。我还在 EventLog
class 中保留了一个 List<LogEvent>
,这样用户就可以在当前日志中搜索特定事件(未完全关闭 implemented/polished)。
这是我目前的 TPL 解决方案。我尽我所能将日志记录功能包装到 Task
s 中,但我仍然觉得我应该有一个类似于 publish
的功能,而不必直接将 LogEvent
s 放入a BlockingCollection<>
这样我就可以 运行 在与主 UI.
还有没有一种更简洁的方法可以停止 Task
,而不必从它们的循环中向 break
发送 "special" LogEvent
?
public class EventLog
{
public static EventLog Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<EventLog> lazyInstance = new Lazy<EventLog>(() => new EventLog());
public String LogPath { get; set; }
public ConcurrentQueue<LogEvent> Events { get; set; }
private EventLog()
{
Events = new ConcurrentQueue<LogEvent>();
WriteQueue = new BlockingCollection<LogEvent>();
LogEventQueue = new BlockingCollection<LogEvent>();
LogPath = Assembly.GetExecutingAssembly().CodeBase;
LogPath = Path.GetDirectoryName(LogPath);
LogPath = LogPath.Replace("file:\", "");
LogPath = LogPath + "\LogASDF.txt";
StartManager();
StartWriter();
}
public BlockingCollection<LogEvent> LogEventQueue { get; set; }
private void StartManager()
{
var writeTask = Task.Factory.StartNew(() =>
{
while (true)
{
LogEvent anEvent = LogEventQueue.Take();
Events.Enqueue(anEvent);
WriteQueue.Add(anEvent);
if (anEvent.Message.Contains("Application Terminated"))
break;
}
});
}
private BlockingCollection<LogEvent> WriteQueue { get; set; }
private void StartWriter()
{
var writeTask = Task.Factory.StartNew(() =>
{
using (StreamWriter logFile = new StreamWriter(LogPath, true))
{
while(true)
{
LogEvent anEvent = WriteQueue.Take();
logFile.WriteLine(anEvent.Message);
logFile.Flush();
if (anEvent.Message.Contains("Application Terminated"))
break;
}
logFile.Close();
}
});
}
}
- 如何正确使用
CancellationToken
来取消这两个任务?我看不出BlockingCollection
是如何阻塞的,我总是必须 "pulse" 集合才能解除阻塞。 - 有没有一种 "cleaner" 方法可以将
LogEvent
插入到日志中而不必直接将其插入到LogEventQueue
中?
现在你的代码不是 thread-safe
因为你有这个:
public List<LogEvent> Events { get; private set; }
List<T>
不是线程安全的,可以从外部代码更改。而且我根本看不出它是否被使用。
另外,你真的应该在你的代码中使用 CancellationToken
因为在其他情况下你会遇到麻烦:例如,你有 5 条消息在队列中,你决定取消你的工作。在这种情况下,检查 Shutdown Log
只会在一段时间后中断循环,这会让 class.
此外,BlockingCollection<T>.Take
方法有一个重载 CancellationToken
,但如果取消,您将获得 OperationCanceledException
:
try
{
LogEvent anEvent = WriteQueue.Take(CancellationPending);
}
catch (OperationCanceledException ex)
{
// handle stop here;
}
死循环在多线程中非常糟糕的做法,建议不要使用。
以下是我使用 .net 4.5 处理此问题的方法。对事件队列的所有访问都是同步的,因此不需要锁定或同步:
public class EventLog
{
public String LogPath { get; set; }
public List<LogEvent> Events {get;set;}
private isProcessing = false;
public CancellationTokenSource cts = new CancellationTokenSource();
private CancellationToken _token;
public static EventLog Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<EventLog> lazyInstance = new Lazy<EventLog>(() => new EventLog());
private EventLog()
{
Events = new List<LogEvent>();
Events.CollectionChanged += Events_CollectionChanged;
LogPath = Assembly.GetExecutingAssembly().CodeBase;
LogPath = Path.GetDirectoryName(LogPath);
LogPath = LogPath.Replace("file:\", "");
LogPath = LogPath + "\Log.txt";
_token = cts.Token;
}
public override void publish(LogEvent newEvent)
{
Events.Add(newEvent);
if (!isProcessing)
ProcessLog();
}
private async void ProcessLog()
{
while (Events.Count > 0)
{
isProcessing = true;
LogEvent e = EventLogs.First();
await Task.Run (() => { WriteLog(e,token); },_token);
EventLogs.Remove(e);
if (_token.IsCancellationRequested == true)
EventLogs.Clear();
}
isProcessing = false;
}
private void WriteLog(LogEvent e,CancellationToken token)
{
using (StreamWriter logFile = new StreamWriter(LogPath, true))
{
if (token.IsCancellationRequested == false)
{
logFile.WriteLine(e.Message);
logFile.Flush();
}
}
}
}
编辑:添加了取消标记。 编辑 2:添加了 WriteLog 函数。