博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.Net 并发写入文件的多种方式
阅读量:6118 次
发布时间:2019-06-21

本文共 5372 字,大约阅读时间需要 17 分钟。

1、简介

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

 

2、使用File.AppendAllText写入日志

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

static string _filePath = @"C:\Users\zhengchao\Desktop\测试文件.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();        ///         /// 线程安全的写入文件操作        ///         ///         public static void WriteFile(Action action)        {            cacheLock.EnterWriteLock();            try            {                action.Invoke();            }            finally            {                cacheLock.ExitWriteLock();            }        }    }

调用代码如下所示:

static string _filePath = @"C:\Users\zhengchao\Desktop\测试文件.txt";        static void Main(string[] args)        {            WriteLogSync();            Console.ReadKey();        }        ///         /// 多线程同步写入文件        ///         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()}\r\n";                        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:\Users\zhengchao\Desktop\测试文件.txt";        static void Main(string[] args)        {            WriteLogAsync();            Console.ReadKey();        }        ///         /// 多线程异步写入文件        ///         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()}\r\n";                        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利用率.暂时还没找到多线程写入一个文件,不需要加读锁的方法,如果有,请告知.

 

转载于:https://www.cnblogs.com/GreenLeaves/p/10617306.html

你可能感兴趣的文章
图形界面
查看>>
【HDU】6012 Lotus and Horticulture (BC#91 T2)
查看>>
redis日常使用汇总--持续更新
查看>>
Linux 安装 JDK
查看>>
leetcode-283-Move Zeroes
查看>>
Docker Data Center系列(二)- UCP安装指南
查看>>
Vue 计算属性与侦听器
查看>>
UITableView汇总
查看>>
Protractor的安装及其遇到的问题
查看>>
【转】C#中ToString格式大全
查看>>
Android缓存图片,在系统图库却看不见。怎么做到的?答:新建“.nomedia”的文件即可。...
查看>>
eclipse中在整个工程中查找一个字符串的步骤
查看>>
数据类型转换
查看>>
[转] Android开发者必备的42个链接
查看>>
16.Java5的CountDownLatch同步工具
查看>>
centos7.2环境下安装smokeping对网络状态进行监控
查看>>
zabbix通过php脚本模拟业务访问redis验证nosql的可用性
查看>>
【算法学习笔记】04.C++中结构体定义练习(bign初步)
查看>>
mac os idea的快捷键
查看>>
Java虚拟机(四)--垃圾回收
查看>>