文章目录
- 1 问题引入 及 分析
- 1.1 特点
- 2 高效日志写入 (多个线程同时调用)
- 3 .代码优化(避免lock对主线程的影响)
-
- 3.1 优化后的源码
- 3.2 化后代码的优势
- 3.3 日志调用示例
- 3.4 如果不调用 StopLogger() 会发生什么?
- 3.5 如何确保 StopLogger() 被调用
- 4 AutoFlush = true 和 Flush() 适用场景
- 5 降低磁盘负担,提高写入性能
-
- 5.1 完整代码
- 5.2 多线程日志写入示例
1 问题引入 及 分析
程序在运行过程中,偶发性的出现故障,时有时无。
为了对运行过程以及程序故障的跟踪,我们对串口数据监控事件的和TCP监控事件中的数据及状态都写入日志记录。
下面串口事件和tcp事件TcpClientHelper.getMsgInvoked += TcpClientHelper_getMsgInvoked;和 SP.ReceivedBytesEventHandler += SP_ReceivedBytesEventHandler; ,对于事件可以理解为线程,这里同时启动两个事件,就相当于开了两个线程。
那么既然那是 对运行过程以及程序故障的跟踪,我们的日志需要具备以下特点:
1.1 特点
-
1 .使用 StreamWriter 并开启 AutoFlush = true:避免频繁打开/关闭文件,提高写入效率。
-
2.使用 File.AppendText():高效地追加写入,不需要每次打开新文件。
-
3.多个线程同时调用:在高并发场景下优化写入性能。
-
4.日志按日期分类存储:每天生成一个新的日志文件,避免单个文件过大影响性能。
-
5.不能能影响 事件处理函数(主线程)的执行效率,尤其是在高频调用的情况下。
-
6 日志不会丢失 ,即使程序崩溃,日志数据也会写入文件。
2 高效日志写入 (多个线程同时调用)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Helper
{
public static class Logger
{
// **日志文件存储目录**
private static readonly string LogDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \”Log\”);
// **当前日志文件的路径(按日期命名)**
private static string logFile = GetLogFilePath();
// **记录上一次写日志的日期**
private static DateTime lastLogDate = DateTime.Now.Date;
// **线程锁,确保多线程环境下的安全写入**
private static readonly object lockObj = new object();
/// <summary>
/// 计算并返回当前日期对应的日志文件路径
/// </summary>
private static string GetLogFilePath()
{
return Path.Combine(LogDirectory, DateTime.Now.ToString(\”yyyy-MM-dd\”) + \”.log\”);
}
/// <summary>
/// 写日志(高效方式)
/// </summary>
/// <param name=\”message\”>要记录的日志信息</param>
public static void WriteLog(string message,bool bNormal)
{
lock (lockObj) // 确保多线程写入安全
{
// **检查日期是否变更**
if (DateTime.Now.Date > lastLogDate)
{
lastLogDate = DateTime.Now.Date; // 更新日期
logFile = GetLogFilePath(); // 重新计算日志文件路径
}
try
{
// **确保日志文件夹存在**
if (!Directory.Exists(LogDirectory))
{
Directory.CreateDirectory(LogDirectory);
}
// **使用 StreamWriter 进行高效日志写入**
using (StreamWriter writer = new StreamWriter(logFile, true, Encoding.UTF8))
{
writer.AutoFlush = true;//数据立即写入磁盘,日志不会丢失,即使程序崩溃,日志数据也会写入文件。
// **写入日志内容**
if (bNormal)
writer.WriteLine($\”[{
DateTime.Now:yyyy-MM-dd HH:mm:ss}]–[NORMAL]: {
message}\”);
else
writer.WriteLine($\”[{
DateTime.Now:yyyy-MM-dd HH:mm:ss}]–[ERROR]: {
message}\”);
//writer.Flush(); // 手动刷新缓冲区,仅在需要时调用,减少磁盘频繁刷新负担
}
}
catch (Exception ex)
{
**异常处理(防止日志写入异常影响程序运行)**
//Console.WriteLine(\”日志写入失败:\” + ex.Message);
}
}
}
}
}
足实时性和多线程安全性的需求,但 lock 作用域内打开文件并写入日志,会阻塞主线程,这可能会影响 事件处理函数 的执行效率 尤其是在高频调用的情况下。
所以进一步改进
3 .代码优化(避免lock对主线程的影响)
上面的代码已经 满足实时性和多线程安全性的需求,但 lock 作用域内打开文件并写入日志,会阻塞主线程,这可能会影响 事件处理函数 的执行效率 尤其是在高频调用的情况下。
解决方案:
-
1.使用 ConcurrentQueue 作为日志缓冲区,并在单独的后台线程中写入日志,减少对主线程的影响。
-
2.使用 Task.Run()异步 或 后台线程 处理日志写入,减少 事件处理函数 受到的影响。
3.1 优化后的源码
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Helper
{
public static class Logger
{
// **日志存储目录**
private static readonly string
评论前必须登录!
注册