打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
100行代码实现了多线程,批量写入,文件分块的日志方法(上)


来源:Sam Xiao

链接:cnblogs.com/xcj26/p/6037808.html


一,您选择用什么样的日志组件


日志组件,不得不提大名鼎鼎的Log4Net。比较常用的还有 Enterprise Library Logging,ServiceStack Logging。当然您还可以补充,我就只用过这几款。


上边提到的3款日志组件,都要在.config里加代码,特别是Log4Net,还要把SQL写在配置里。我就是仅仅只写个日志,还要配置这么多信息,让人略有不爽。


所以在很长一段时间里,我用下边这个方法写日志:


private static void WriteText(string logPath, string logContent)

{

            try

            {

                if (!File.Exists(logPath))

                {

                    File.CreateText(logPath).Close();

                }

                StreamWriter sw = File.AppendText(logPath);

                sw.Write(logContent);

                sw.Close();

            }

            catch (Exception ex){}

            finally{}

}


这个方法足够的简单,核心代码就只有那么5,6行,还包含容错机制。我就喜欢用这种简单的代码来处理简单的事。


二,多线程下引爆了问题


在多线程的情况下,比如100个线程同时需要写日志,上边提到的这个方法就力不从心了。


一个线程访问日志资源,另一个线程再去访问的时候,就会出现异常。


方法一:


public static Object _processLock = new Object();

private void Button_Click_1(object sender, RoutedEventArgs e)

{

        lock (_processLock)

        {


        }

}


方法二:


public static Object _processLock = new Object();

private void Button_Click_1(object sender, RoutedEventArgs e)

{

   Monitor.Enter(_processLock);

        Monitor.Exit(_processLock);

}


这样,你不得不承认,我已经解决了多线程的问题。


但是有瓶颈,这些需要写日志的线程,必须要等前一个释放了锁资源,后一个线程才能访问的情况。


三,重新设计日志组件



先看图,再说一下我的思路:


1,不管有多少线程同时需要写日志,我都用一个临时队列来存放这些日志信息。


2,再启用一个Task任务把队列的日志批量存放到.log文件里。


3,附加一个小功能,每个日志存储的大小限制,当日志太大了,查看打开的时候比较慢。


四,具体的代码实现


1,在多线程的情况下,我们首先把日志压到Queue队列里


static ConcurrentQueue<>> logQueue = new ConcurrentQueue<>>();


在这儿,我为什么选用 ConcurrentQueue  而不是  Queue  。因为ConcurrentQueue  表示线程安全的先进先出 (FIFO) 集合。


当然你一定要用Queue也是可以的,但是要自己去实现锁机制,何必自找麻烦呢?


2,把日志队列里的数据,批量持久化到.log文件里


这个问题,让我很头大。我最开始的方法是:


持久化日志方法一:


a,申明一个Task任务,当我Task任务没有实现化时,先实例化,然后再进行持久化日志写入。


b,当我的Task任务,已经实例化了,并且是处于 IsCompleted 状态,我重新实例化Task,再进行持久化日志的写入。


static Task writeTask = default(Task);


public static void WriteLog(String customDirectory, String preFile, String infoData)

    {

        string logPath = GetLogPath(customDirectory, preFile);

        string logContent = String.Concat(DateTime.Now, ' ', infoData);

        logQueue.Enqueue(new Tuple(logPath, logContent));

        if (writeTask == null)

        {

            writeTask = new Task((object obj) =>

            {

                //pause.WaitOne(1000, true);

                LogRepository();

            }

                , null

                , TaskCreationOptions.LongRunning);

            writeTask.Start();

        }

        if (writeTask.IsCompleted)

        {

            writeTask = new Task((object obj) =>

            {

                //pause.WaitOne(1000, true);

                LogRepository();

            }

                , null

                , TaskCreationOptions.LongRunning);

            writeTask.Start();

        }

    }


异常信息:


理论是那么的美好,但是现实是那么残酷,当我跑单元测试的时候,一段时间后总是抛出如下错误。如果是有那位朋友知道其原因,把这个问题解决就完美了。



但是我不能因为这一个异常,导致我这个组件写不下去吧!活人不能被一泡尿给憋死。


追加:完整代码如下:


public class IOExtention

{

     static ConcurrentQueue<>> logQueue = new ConcurrentQueue<>>();

        static volatile Task writeTask = default(Task);

        static IOExtention(){

        }

        public static void WriteLog(String preFile, String infoData)

        {

            WriteLog(string.Empty, preFile, infoData);

        }

        static AutoResetEvent pause = new AutoResetEvent(false);

     public static void WriteLog(String customDirectory, String preFile, String infoData)

        {

            string logPath = GetLogPath(customDirectory, preFile);

            string logContent = String.Concat(DateTime.Now, ' ', infoData);

            logQueue.Enqueue(new Tuple(logPath, logContent));

            if (writeTask == null)

            {

                writeTask = new Task((object obj) =>

                {

                    //pause.WaitOne(1000, true);

                    LogRepository();

                }

                    , null

                    , TaskCreationOptions.LongRunning);

                writeTask.Start();

            }

            if (writeTask.IsCompleted)

            {

                writeTask = new Task((object obj) =>

                {

                    //pause.WaitOne(1000, true);

                    LogRepository();

                }

                  , null

                  , TaskCreationOptions.LongRunning);

                writeTask.Start();

            }

        }

        public static void LogRepository()

        {

            List temp = new List();

            foreach (var logItem in logQueue)

            {

                string logPath = logItem.Item1;

                string logMergeContent = String.Concat(logItem.Item2, Environment.NewLine, '-----------------------------------------------------------', Environment.NewLine);

                string[] logArr = temp.FirstOrDefault(d => d[0].Equals(logPath));

                if (logArr != null)

                {

                    logArr[1] = string.Concat(logArr[1], logMergeContent);

                }

                else

                {

                    logArr = new string[] { logPath, logMergeContent };

                    temp.Add(logArr);

                }

                Tuple val = default(Tuple);

                logQueue.TryDequeue(out val);

            }

            foreach (string[] item in temp)

            {

                WriteText(item[0], item[1]);

            }

        }

        private static string GetLogPath(String customDirectory, String preFile)

        {

            string newFilePath = string.Empty;

            String logDir = string.IsNullOrEmpty(customDirectory) ? Path.Combine(Environment.CurrentDirectory, 'logs') : customDirectory;

            if (!Directory.Exists(logDir))

            {

                Directory.CreateDirectory(logDir);

            }

            string extension = '.log';

            string fileNameNotExt = String.Concat(preFile, DateTime.Now.ToString('yyyyMMdd'));

            String fileName = String.Concat(fileNameNotExt, extension);

            string fileNamePattern = string.Concat(fileNameNotExt, '(*)', extension);

            List filePaths = Directory.GetFiles(logDir, fileNamePattern, SearchOption.TopDirectoryOnly).ToList();

            if (filePaths.Count > 0)

            {

                int fileMaxLen = filePaths.Max(d => d.Length);

                string lastFilePath = filePaths.Where(d => d.Length == fileMaxLen).OrderByDescending(d => d).FirstOrDefault();

                if (new FileInfo(lastFilePath).Length > 1 * 1024 * 1024)

                {

                    string no = new Regex(@'(?is)(?<>

                    int tempno = 0;

                    bool parse = int.TryParse(no, out tempno);

                    string formatno = String.Format('({0})', parse ? (tempno + 1) : tempno);

                    string newFileName = String.Concat(fileNameNotExt, formatno, extension);

                    newFilePath = Path.Combine(logDir, newFileName);

                }

                else

                {

                    newFilePath = lastFilePath;

                }

            }

            else

            {

                string newFileName = String.Concat(fileNameNotExt, String.Format('({0})', 0), extension);

                newFilePath = Path.Combine(logDir, newFileName);

            }

            return newFilePath;

        }

        private static void WriteText(string logPath, string logContent)

        {

            try

            {

                if (!File.Exists(logPath))

                {

                    File.CreateText(logPath).Close();

                }

                StreamWriter sw = File.AppendText(logPath);

                sw.Write(logContent);

                sw.Close();

            }

            catch (Exception ex)

            {


            }

            finally

            {


            }

        }

    }


关注「DotNet」 

看更多精选 .Net 技术文章

↓↓↓

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C#使用读写锁处理多线程并发写入文件问题
使用文件流记录日志
使用Struts2拦截器实现日志管理系统
.NET 日志系统设计
自己最近写的一组日志处理类(支持高并发处理)
C#读文本文件
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服