C# .NET多线程异步记录日声,队列LOG

如果你希望以最简单的方式实现,同时保证线程安全和基本的异步支持,可以使用以下简化方案。这个方案直接使用 File.AppendAllTextAsync 并添加锁机制,适合错误情况较少的场景:

简化实现方案csharp

复制代码
private readonly object _fileLock = new object();
private readonly string _logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "client_close.log");

// 移除客户端
public void RemoveClient(string clientId, string errinfo = null)
{
    if (_clients.TryRemove(clientId, out _) && 
        _lastHeartbeat.TryRemove(clientId, out _))
    {
        // 记录日志(异步但顺序执行)
        _ = LogClientRemovalAsync(clientId, errinfo);
    }
}

// 异步记录客户端移除信息
private async Task LogClientRemovalAsync(string clientId, string errinfo)
{
    string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff},{clientId},{errinfo ?? "正常关闭"}";
    
    // 使用锁确保文件写入的线程安全
    lock (_fileLock)
    {
        try
        {
            // 异步写入文件(在锁内使用异步方法,确保顺序执行)
            File.AppendAllTextAsync(_logFilePath, logEntry + Environment.NewLine).Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"日志写入失败: {ex.Message}");
            // 可添加更复杂的错误处理逻辑
        }
    }
}

关键点说明

  1. 线程安全

    • 使用 lock 关键字确保同一时间只有一个线程可以写入文件。
    • 虽然 File.AppendAllTextAsync 是异步方法,但在锁内部调用其同步版本 Wait(),确保写入顺序。
  2. 异步支持

    • 方法标记为 async,但使用 Wait() 同步等待写入完成,避免异步操作的复杂性。
    • 调用方通过 _ = LogClientRemovalAsync(...) 非阻塞地触发日志记录。
  3. 错误处理

    • 简单捕获并打印异常,适合错误情况较少的场景。

日志文件格式

与之前相同:

复制代码
2023-06-15 14:30:21.123,client123,连接超时
2023-06-15 14:30:35.456,client456,正常关闭

优点

  • 代码简单:无需额外的队列或后台任务。
  • 资源占用少:不使用额外的线程或信号量。
  • 基本异步支持:在锁内使用异步文件操作,减少 IO 阻塞。

注意事项

  • 性能限制:在高并发场景下可能成为瓶颈,因为每次写入都需要获取锁。
  • 异常处理简化:仅简单打印错误,未实现重试或日志缓冲。

如果你的应用程序错误情况确实很少,且并发量不高,这种实现方式足够简单且可靠。


要实现支持异步多线程并发操作的客户端关闭信息记录功能,需要考虑线程安全和文件写入的异步性。以下是一个完整的解决方案:

实现方案

1. 添加异步日志方法

使用 ConcurrentQueue 缓存日志条目,并通过后台任务异步写入文件,确保线程安全和高性能。

复制代码
private readonly ConcurrentQueue<string> _logQueue = new ConcurrentQueue<string>();
private readonly CancellationTokenSource _logCts = new CancellationTokenSource();
private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
private readonly string _logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "client_close.log");

// 初始化时启动日志写入任务
public WebSocketHeartbeatManager()
{
    // 启动异步日志写入任务
    _ = Task.Run(() => ProcessLogQueueAsync(_logCts.Token));
}

// 异步处理日志队列
private async Task ProcessLogQueueAsync(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        try
        {
            if (_logQueue.TryDequeue(out string logEntry))
            {
                await WriteLogEntryAsync(logEntry);
            }
            else
            {
                await Task.Delay(100, cancellationToken); // 短暂等待,避免CPU空转
            }
        }
        catch (OperationCanceledException)
        {
            // 正常取消,退出循环
            break;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"日志处理错误: {ex.Message}");
            // 发生错误时等待更长时间
            await Task.Delay(1000, cancellationToken);
        }
    }
}

// 异步写入日志条目
private async Task WriteLogEntryAsync(string logEntry)
{
    await _fileLock.WaitAsync(); // 获取文件锁
    try
    {
        // 使用File.AppendAllTextAsync确保原子性
        await File.AppendAllTextAsync(_logFilePath, logEntry + Environment.NewLine);
    }
    finally
    {
        _fileLock.Release(); // 释放文件锁
    }
}

// 清理资源
public void Dispose()
{
    _logCts.Cancel();
    _logCts.Dispose();
    _fileLock.Dispose();
}
2. 修改 RemoveClient 方法

添加可选参数 errinfo,并将日志信息加入队列:csharp

复制代码
// 移除客户端
public void RemoveClient(string clientId, string errinfo = null)
{
    if (_clients.TryRemove(clientId, out _) && 
        _lastHeartbeat.TryRemove(clientId, out DateTime removedTime))
    {
        // 格式化日志条目:时间,CLIENTID,errinfo
        string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff},{clientId},{errinfo ?? "正常关闭"}";
        
        // 将日志条目加入队列(线程安全操作)
        _logQueue.Enqueue(logEntry);
    }
}

关键点说明

  1. 线程安全

    • 使用 ConcurrentQueue 存储日志条目,确保多线程环境下的入队操作安全。
    • 通过 SemaphoreSlim 控制文件写入的并发访问,避免文件冲突。
  2. 异步处理

    • 日志写入操作在单独的后台任务中进行,不阻塞主线程。
    • 使用 File.AppendAllTextAsync 实现异步文件写入。
  3. 异常处理

    • 在日志处理循环中捕获并处理可能的异常,确保系统稳定运行。
  4. 资源释放

    • 实现 Dispose 方法,在对象销毁时正确释放 CancellationTokenSourceSemaphoreSlim

日志文件格式示例plaintext

复制代码
2023-06-15 14:30:21.123,client123,连接超时
2023-06-15 14:30:35.456,client456,正常关闭
2023-06-15 14:31:02.789,client789,协议错误

使用示例csharp

复制代码
// 正常关闭
manager.RemoveClient("client123");

// 异常关闭
manager.RemoveClient("client456", "心跳超时");

这种实现方式在高并发场景下依然能保持良好性能,同时确保所有客户端关闭信息都被可靠记录。

相关推荐
楠枬17 分钟前
OpenFeign
java·spring cloud·微服务
CC.GG19 分钟前
【Qt】信号和槽
开发语言·数据库·qt
是席木木啊19 分钟前
基于MinIO Java SDK实现ZIP文件上传的方案与实践
java·开发语言
计算机毕设指导624 分钟前
基于微信小程序的垃圾分类信息系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
毕设源码-邱学长26 分钟前
【开题答辩全过程】以 基于Vue的爱心公益募捐平台的设计与实现为例,包含答辩的问题和答案
前端·javascript·vue.js
毕设源码-赖学姐26 分钟前
【开题答辩全过程】以 高校就业系统的实现为例,包含答辩的问题和答案
java·eclipse
一起养小猫30 分钟前
《Java数据结构与算法》第四篇(四):二叉树的高级操作查找与删除实现详解
java·开发语言·数据结构·算法
TH_130 分钟前
20、误删oracle数据
数据库·oracle
qq 1808095130 分钟前
多智能体编队重构与协同避障方法探索
c#
IT_陈寒32 分钟前
Redis实战精要:5种高频使用场景与性能优化全解析|得物技术
前端·人工智能·后端