zoukankan      html  css  js  c++  java
  • .Net 并发写入文件的多种方式

    1、简介

    本文主要演示日常开发中利用多线程写入文件存在的问题,以及解决方案,本文使用最常用的日志案例!

    2、使用File.AppendAllText写入日志

    这是种常规的做法,通过File定位到日志文件所在位置,然后写入相应的日志内容,代码如下:

            static string _filePath = @"C:UserszhengchaoDesktop测试文件.txt";
            static void Main(string[] args)
            {
                WriteLogAsync();
                Console.ReadKey();
            }
    
            static void WriteLogAsync()
            {
                var logRequestNum = 100000;//请求写入日志次数
                var successCount =0;//执行成功次数
                var failCount = 0;//执行失败次数
                //模拟100000次用户请求写入日志操作
                Parallel.For(0, logRequestNum, i =>
                {
                    try
                    {
                        var now = DateTime.Now;
                        var logContent = $"当前线程Id:{Thread.CurrentThread.ManagedThreadId},日志内容:暂时没有,日志级别:Warn,写入时间:{now.ToString()}";
                        File.AppendAllText(_filePath, logContent);
                        successCount++;
                    }
                    catch (Exception ex)
                    {
                        failCount++;
                        Console.WriteLine(ex.Message);
                    }
                });
                Console.WriteLine($"Request Count:{logRequestNum}. Success Count:{successCount} Failed Count:{failCount}.");
            }

    报错了,原因,Windows不允许多个线程同时操作同一个文件,所以,抛异常.所以必须解决这个问题。

    3、利用ReadWriterSlim解决多线程征用文件问题

    关于ReadWriterSlim的使用,在本人的这篇随笔中已介绍,在其基础上,对SynchronizedCache类稍稍改造,形成一个SynchronizedFile类,对相关操作代码进行线程安全处理,即能解决当前的问题,代码如下:

       public class SynchronizedFile
        {
            private static ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
    
            /// <summary>
            /// 线程安全的写入文件操作
            /// </summary>
            /// <param name="action"></param>
            public static void WriteFile(Action action)
            {
                cacheLock.EnterWriteLock();
                try
                {
                    action.Invoke();
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
            }
        }

    调用代码如下所示:

            static string _filePath = @"C:UserszhengchaoDesktop测试文件.txt";
            static void Main(string[] args)
            {
                WriteLogSync();
                Console.ReadKey();
            }
    
            /// <summary>
            /// 多线程同步写入文件
            /// </summary>
            static void WriteLogSync()
            {
                var logRequestNum = 10000;//请求写入日志次数
                var successCount =0;//执行成功次数
                var failCount = 0;//执行失败次数
    
                var stopWatch = Stopwatch.StartNew();
                //模拟100000次用户请求写入日志操作
                var result=Parallel.For(0, logRequestNum, i =>
                {
                    SynchronizedFile.WriteFile(() =>
                    {
                        try
                        {
                            var now = DateTime.Now;
                            var logContent = $"当前线程Id:{Thread.CurrentThread.ManagedThreadId},日志内容:暂时没有,日志级别:Warn,写入时间:{now.ToString()}
    ";
                            File.AppendAllText(_filePath, logContent);
                            successCount++;
                        }
                        catch (Exception ex)
                        {
                            failCount++;
                            Console.WriteLine(ex.Message);
                        }
                    });
    
                });
                if (result.IsCompleted)
                {
                    stopWatch.Stop();
                    Console.WriteLine($"Request Count:{logRequestNum}. Success Count:{successCount} Failed Count:{failCount},总耗时:{stopWatch.ElapsedMilliseconds/1000}秒");
                }
            }

    内容全部写入成功,但是还没有结束,原因是,反编译

     

    一直反编译下去,会发现

     

    用的是同步Api,所以代码可以继续优化,同步意味着每个线程在写入文件时,当前的写入托管代码会转换成托管代码,最后,Windows会把当前写入操作的数据初始化成IRP数据包传给硬件设备,之后硬件设备开始执行写入操作。这个过程,当前线程在和硬件交互时,不会返回到线程池,而是被Windows置为休眠状态,等待硬件设置执行写入操作完毕后,接着Windows会唤起该线程,最后又回到我的托管代码也就是C#代码中,继续执行下面的逻辑.所以当前的日志写入代码可以优化,使用异步Api来做.这样当前线程不会等待硬件设备,而是返回线程池.提高CPU的利用率.

    4、优化代码

            static string _filePath = @"C:UserszhengchaoDesktop测试文件.txt";
            static void Main(string[] args)
            {
                WriteLogAsync();
                Console.ReadKey();
            }
    
            /// <summary>
            /// 多线程异步写入文件
            /// </summary>
            static void WriteLogAsync()
            {
                var logRequestNum = 10000;//请求写入日志次数
                var successCount = 0;//执行成功次数
                var failCount = 0;//执行失败次数
    
                var stopWatch = Stopwatch.StartNew();
                //模拟100000次用户请求写入日志操作
                var result = Parallel.For(0, logRequestNum, i =>
                {
                    SynchronizedFile.WriteFile(() =>
                    {
                        try
                        {
                            var now = DateTime.Now;
                            var logContent = $"当前线程Id:{Thread.CurrentThread.ManagedThreadId},日志内容:暂时没有,日志级别:Warn,写入时间:{now.ToString()}
    ";
                            var utf8NoBom = new UTF8Encoding(false, true);//去掉Dom头
                            using (StreamWriter writer = new StreamWriter(_filePath, true, utf8NoBom))
                            {
                                writer.WriteAsync(logContent);
                            }
                            successCount++;
                        }
                        catch (Exception ex)
                        {
                            failCount++;
                            Console.WriteLine(ex.Message);
                        }
                    });
    
                });
                if (result.IsCompleted)
                {
                    stopWatch.Stop();
                    Console.WriteLine($"Request Count:{logRequestNum}. Success Count:{successCount} Failed Count:{failCount},总耗时:{stopWatch.ElapsedMilliseconds / 1000}秒");
                }
    
            }

    虽然效果差不多,但是能提升CPU利用率.暂时还没找到多线程写入一个文件,不需要加读锁的方法,如果有,请告知.

  • 相关阅读:
    Adding a prefix header to an iOS project
    DZ论坛常见基本设置问题
    DZ论坛如何去掉“今日”“昨日”发帖数显示?
    Discuz源码
    怎样使Firefox的新建标签页为空白页
    discuz论坛
    TK域名首次注册教程(咸干花生)
    氪星年货 #1:那些来自大牛的真知灼见
    慢性子
    life and penis
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/10617306.html
Copyright © 2011-2022 走看看