ManualResetEvent

ManualResetEvent 类似于AutoResetEvent,不过要手动设置Reset;并且ManualResetEvent可以控制多个线程协调。

ManualResetEventSlim 是轻型级的类,可以替代ManualResetEvent 使用。

Wait()内部轮询检测。判断IsSet属性,如果为false,线程会挂起;如果为true,线程不会挂起,会继续执行。
Reset()重置信号,IsSet=false。
Set() 设置信号,IsSet=true。

ManualResetEventSlim实现日志,通知线程在队列中有新的内容,当队列中写完日志后,重置信号,等待下次日志信息。

using log4net;
using log4net.Config;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

/* 优点:适用高并发场景,高性能写日志
   缺点:程序崩溃或电脑断电,队列中的日志可能未写完而丢失
 */
public class LogHelper
{
    // 延迟加载
    private static readonly Lazy<LogHelper> _lazy = new Lazy<LogHelper>(() => new LogHelper());
    /// <summary>
    /// 单例
    /// </summary>
    public static LogHelper Instance() => _lazy.Value;
    private LogHelper()
    {
        var configFile = "log4net.config";
        var fi = new FileInfo(configFile);
        if (!fi.Exists)
        {
            throw new Exception("log4net.config is NULL!");
        }
        XmlConfigurator.Configure(fi);
        _log = LogManager.GetLogger(typeof(LogHelper));

        _queue = new ConcurrentQueue<LogMessage>();
        // 创建无信号状态
        _mre = new ManualResetEventSlim(false);


    }
    /// <summary>
    /// 日志
    /// </summary>
    private readonly ILog _log;
    /// <summary>
    /// 信号
    /// </summary>
    private readonly ManualResetEventSlim _mre;
    /// <summary>
    /// 队列
    /// </summary>
    private readonly ConcurrentQueue<LogMessage> _queue;

    // 线程记录日志,只在程序初始化时调用一次
    public void Register()
    {
        Task.Run(WriteLogAsync);
    }

    private async Task WriteLogAsync()
    {
        while (true)
        {
            // 等待。收到被设置信号true,不再阻塞,继续执行后续代码
            _mre.Wait();
            // 从队列写入磁盘
            LogMessage msg;
            while (_queue.Count > 0 && _queue.TryDequeue(out msg))
            {
                switch (msg.Level)
                {
                    case LogLevel.Debug:
                        _log.Debug(msg.Message, msg.Exception);
                        break;
                    case LogLevel.Info:
                        _log.Info(msg.Message, msg.Exception);
                        break;
                    case LogLevel.Warn:
                        _log.Warn(msg.Message, msg.Exception);
                        break;
                    case LogLevel.Error:
                        _log.Error(msg.Message, msg.Exception);
                        break;
                    case LogLevel.Fatal:
                        _log.Fatal(msg.Message, msg.Exception);
                        break;
                    default:
                        break;
                }
            }
            // 必须手动重置信号false
            _mre.Reset();
            await Task.Delay(1);
        }
    }

    public void EnqueueMessage(string message, LogLevel level, Exception ex = null)
    {
        if ((level == LogLevel.Debug && _log.IsDebugEnabled) ||
            (level == LogLevel.Info && _log.IsInfoEnabled) ||
            (level == LogLevel.Warn && _log.IsWarnEnabled) ||
            (level == LogLevel.Error && _log.IsErrorEnabled) ||
            (level == LogLevel.Fatal && _log.IsFatalEnabled))
        {
            _queue.Enqueue(new LogMessage
            {
                Message = message,
                Level = level,
                Exception = ex,
            });
            // 设置信号true。通知线程
            _mre.Set();
        }
    }

    public static void Debug(string msg, Exception ex = null)
    {
        Instance().EnqueueMessage(msg, LogLevel.Debug, ex);
    }
    public static void Info(string msg, Exception ex = null)
    {
        Instance().EnqueueMessage(msg, LogLevel.Info, ex);
    }
    public static void Warn(string msg, Exception ex = null)
    {
        Instance().EnqueueMessage(msg, LogLevel.Warn, ex);
    }
    public static void Error(string msg, Exception ex = null)
    {
        Instance().EnqueueMessage(msg, LogLevel.Error, ex);
    }
    public static void Fatal(string msg, Exception ex = null)
    {
        Instance().EnqueueMessage(msg, LogLevel.Fatal, ex);
    }
}
/// <summary>
/// 日志内容
/// </summary>
public class LogMessage
{
    public string Message { get; set; }
    public LogLevel Level { get; set; }
    public Exception Exception { get; set; }
}
/// <summary>
/// 日志等级
/// </summary>
public enum LogLevel
{
    Debug,
    Info,
    Warn,
    Error,
    Fatal,
}

参考链接:同步基元概述

posted @ 2022-03-09 15:48  wesson2019  阅读(91)  评论(0编辑  收藏  举报