.Net 并发写入文件的多种方式
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 = ;//请求写入日志次数
var successCount =;//执行成功次数
var failCount = ;//执行失败次数
//模拟100000次用户请求写入日志操作
Parallel.For(, 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:\Users\zhengchao\Desktop\测试文件.txt";
static void Main(string[] args)
{
WriteLogSync();
Console.ReadKey();
} /// <summary>
/// 多线程同步写入文件
/// </summary>
static void WriteLogSync()
{
var logRequestNum = ;//请求写入日志次数
var successCount =;//执行成功次数
var failCount = ;//执行失败次数 var stopWatch = Stopwatch.StartNew();
//模拟100000次用户请求写入日志操作
var result=Parallel.For(, 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();
} /// <summary>
/// 多线程异步写入文件
/// </summary>
static void WriteLogAsync()
{
var logRequestNum = ;//请求写入日志次数
var successCount = ;//执行成功次数
var failCount = ;//执行失败次数 var stopWatch = Stopwatch.StartNew();
//模拟100000次用户请求写入日志操作
var result = Parallel.For(, 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利用率.暂时还没找到多线程写入一个文件,不需要加读锁的方法,如果有,请告知.
.Net 并发写入文件的多种方式的更多相关文章
- 用读写锁三句代码解决多线程并发写入文件 z
C#使用读写锁三句代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题 在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三 ...
- Linux中删除特殊名称文件的多种方式
今日分享:我们在肉体的疾病方面花了不少钱,精神的病害方面却没有花什么,现在已经到了时候,我们应该有不平凡的学校.--<瓦尔登湖> 前言 我们都知道,在linux删除一个文件可以使用rm命令 ...
- C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题
(补充:初始化FileStream时使用包含文件共享属性(System.IO.FileShare)的构造函数比使用自定义线程锁更为安全和高效,更多内容可点击参阅) 在开发程序的过程中,难免少不了写入错 ...
- C#使用读写锁解决多线程并发写入文件时线程同步的问题
读写锁是以 ReaderWriterLockSlim 对象作为锁管理资源的,不同的 ReaderWriterLockSlim 对象中锁定同一个文件也会被视为不同的锁进行管理,这种差异可能会再次导致文件 ...
- ansible下载文件的多种方式
对于ansible来说,下载文件是一个很重要的课题,这是build或者deploy的第一步,通常来讲由于不同项目的差异,可能我们的代码包或者资源文件保存在于http,github,nexus,ftp, ...
- PHP导出excel文件的多种方式
1.第一种实现的方法 set_time_limit(0); //逐条导出数据 ob_end_clean(); header("Content-type: application/vnd.ms ...
- 11. python读写文件的多种方式
一.txt文件 with open('users.txt','r') as user_file: data = user_file.readlines() users = [] for line in ...
- Java 读取TXT文件的多种方式
1).按行读取TXT文件package zc;import java.io.BufferedReader;import java.io.File;import java.io.FileNotFound ...
- SQL映射文件实现多种方式查询
1.单条件查询在test中代码如下 2.多条件查询时需要把查询条件编辑为对象或者是集合传入,例如 通过对象进行查询 或者是通过集合进行查询列如Map集合 还有就是通过@Param注解实现多参数的入参, ...
随机推荐
- concurrent.futures性能阐述
python因为其全局解释器锁GIL而无法通过线程实现真正的平行计算.这个论断我们不展开,但是有个概念我们要说明,IO密集型 vs. 计算密集型. IO密集型:读取文件,读取网络套接字频繁. 计算密集 ...
- PHP PDO预定义常量
以下常量由本扩展模块定义,因此只有在本扩展的模块被编译到PHP中,或者在运行时被动态加载后才有效. 注意: PDO使用类常量自PHP 5.1.以前的版本使用的全局常量形式PDO_PARAM_BOOL中 ...
- C++枚举类型Enum及C++11强枚举类型用法
C++中的枚举类型常常和switch配合使用,这里用一个简单的switch控制键盘回调的代码片段来说明枚举的用法: //W A S D 前.后.左.右行走 enum Keydown{ Forward= ...
- [译]使用explain API摆脱ElasticSearch集群RED苦恼(转)
"哔...哔...哗",PagerDuty的报警通知又来了. 可能是因为你又遭遇了节点宕机, 或者服务器机架不可用, 或者整个ElasticSearch集群重启了. 不管哪种情况, ...
- python之路(七)-递归算法
递归 特点 递归算法是一种直接或者间接地调用自身算法的过程.在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解. 递归算法解决问题的特点: (1) 递归就是在 ...
- Windows下安装BeautifulSoup4显示'You are trying to run the Python 2 version of Beautiful Soup under Python 3.(`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).'
按照网上教程,将cmd的目录定位到解压缩文件夹地址,然后 >>python setup.py install ( Window下不能直接解压tar.giz文件,可以使用7z解压软件提取解压 ...
- HDU - 1241 Oil Deposits 经典dfs 格子
最水的一道石油竟然改了一个小时,好菜好菜. x<=r y<=c x<=r y<=c x<=r y<=c x<=r y<=c #include ...
- 【python-时间戳】时间与时间戳之间的转换
对于时间数据,如2016-05-05 20:28:54,有时需要与时间戳进行相互的运算,此时就需要对两种形式进行转换,在Python中,转换时需要用到time模块,具体的操作有如下的几种: 将时间转换 ...
- 网络操作系统 第七章 管理TCP/IP网络
本章小结 本章介绍了TCP/IP的相关概念,并且在此处基础上,介绍了Windows Server 2008中使用TCP/IP网络配置工具实现网络连接和管理的方法,在Linux系统中,讲解了是如何使用图 ...
- 一窍懂PID
这是学习PID时的理解,做个笔记! 一:首选,说一下一些基本概念,为了更好理解. PID:就是对输入偏差进行比例积分微分运算,运算的叠加结果去控制执行机构. P(proportion):比例,就是对输 ...