最新文章 (全部类别)
安装DevExpress V22.1.13
官方VS2022下载,VS2022密钥
C/S快速开发框架旗舰版CSFrameworkV6.0 - VS开发环境配置
运行.NET8 Winform程序弹窗用户账户控制解决方案
使用Xlight FTP文件服务器
CSFramework试用版定制用户软件信息
DevExpress TreeList树形表格组件使用
DevExpress GridView表格不启用/禁用主从表模式(Master/Detail)
CSFrameworkV6开发框架集成GZUpdate.Client自动升级程序
FastReport.NET2023破解版去除水印DEMO VERSION (2023.1.8/2023.2.18版本)
.NET8调用存储过程传入DataTable表类型解决方案
使用DeepSeek生成DevExpress表格、窗体、C#模型代码并集成到开发框架
C# EF+LINQ 查询主表明细表数据查询条件写法
C/S架构软件开发平台 - 旗舰版V6.1 - 序列号管理
塑木地板行业ERP - 用户操作手册 - 物料类别管理
QMS五金制品行业报价系统 - 用户操作手册 - 成本基础资料表
QMS五金制品行业报价系统 - 用户操作手册 - 采购评估
QMS五金制品行业报价系统 - 用户操作手册 - 设备登记
QMS五金制品行业报价系统 - 用户操作手册 - 新品可行性评估
QMS五金制品行业报价系统 - 用户操作手册 - 工艺工序维护
QMS五金制品行业报价系统 - 用户操作手册 - 业务员管理
QMS五金制品行业报价系统 - 用户操作手册 - 客户管理
塑木地板行业ERP - 用户操作手册 - 组织架构(部门)
塑木地板行业ERP - 用户操作手册 - 系统初始化
塑木地板行业ERP - 用户操作手册 - 付款AP
塑木地板行业ERP - 用户操作手册 - 供应商对账单
塑木地板行业ERP - 用户操作手册 - 收款AR
塑木地板行业ERP - 用户操作手册 - 客户对账
塑木地板行业ERP - 用户操作手册 - 销售退货
塑木地板行业ERP - 用户操作手册 - 采购入库
塑木地板行业ERP - 用户操作手册 - 采购退货
塑木地板行业ERP - 用户操作手册 - 采购订单
塑木地板行业ERP - 用户操作手册 - 供应商管理
CSFrameworkV6.0 - 快速开发平台 - 增加字段操作步骤
录入基础资料数据,编码自动生成,自动编号
塑木地板行业ERP - 用户操作手册 - 客户送货单(成品出库)
塑木地板行业ERP - 用户操作手册 - 客户管理
VS2022项目自动刷新依赖项程序集引用(出现黄色叹号)
.NETCore WebApi服务器提供本地文件服务(通过URL访问图片)
C/S架构软件开发平台 - 旗舰版V6.0 - 底层框架迭代开发
.NET8 Winform桌面应用程序禁用dpi缩放
GIT改了密码导致无法提交代码解决方案
OneDrive安装闪退解决方案
VS2022正在加载设计器,连接到已命名的管道时发生超时
客户端部署.NET7程序 | 客户端安装环境
DevExpress.XtraTreeList.TreeList 组件自动定位当前记录
塑木地板行业ERP - 用户操作手册 - 软件安装&部署
塑木地板行业ERP - 用户操作手册 - 用户管理
塑木地板行业ERP - 用户操作手册 - 角色管理
塑木地板行业ERP - 用户操作手册 - 成品出入库历史记录
.net敏捷开发,创造卓越

C#多线程使用读写锁ReaderWriterLockSlim同步写入文件


C#多线程使用读写锁ReaderWriterLockSlim同步写入文件

复杂多线程环境下使用读写锁同步写入文件

代码一:

   class Program
    {
        static int LogCount = 1000;
        static int SumLogCount = 0;
        static int WritedCount = 0;
        static int FailedCount = 0;
        static void Main(string[] args)
        {
            //往线程池里添加一个任务,迭代写入N个日志
            SumLogCount += LogCount;
            ThreadPool.QueueUserWorkItem((obj) =>
            {
                Parallel.For(0, LogCount, e =>
                {
                    WriteLog();
                });
            });
            //在新的线程里,添加N个写入日志的任务到线程池
            SumLogCount += LogCount;
            var thread1 = new Thread(() =>
            {
                Parallel.For(0, LogCount, e =>
                {
                    ThreadPool.QueueUserWorkItem((subObj) =>
                    {
                        WriteLog();
                    });
                });
            });
            thread1.IsBackground = false;
            thread1.Start();
            //添加N个写入日志的任务到线程池
            SumLogCount += LogCount;
            Parallel.For(0, LogCount, e =>
            {
                ThreadPool.QueueUserWorkItem((obj) =>
                {
                    WriteLog();
                });
            });
            //在新的线程里,迭代写入N个日志
            SumLogCount += LogCount;
            var thread2 = new Thread(() =>
            {
                Parallel.For(0, LogCount, e =>
                {
                    WriteLog();
                });
            });
            thread2.IsBackground = false;
            thread2.Start();
            //在当前线程里,迭代写入N个日志
            SumLogCount += LogCount;
            Parallel.For(0, LogCount, e =>
            {
                WriteLog();
            });
            Console.WriteLine("Main Thread Processed.\r\n");

            while (true)
            {
                Console.WriteLine(string.Format("Sum Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", SumLogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
                Console.ReadLine();
            }
        }
        //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入
        static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
        static void WriteLog()
        {
            try
            {
                //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入
                //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
                //从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度
                //因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常
                LogWriteLock.EnterWriteLock();
                var logFilePath = "log.txt";
                var now = DateTime.Now;
                var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
                File.AppendAllText(logFilePath, logContent);
                WritedCount++;
            }
            catch (Exception)
            {
                FailedCount++;
            }
            finally
            {
                //退出写入模式,释放资源占用
                //注意:一次请求对应一次释放
                // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放]
                // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定]
                LogWriteLock.ExitWriteLock();
            }
        }
    }

运行结果:

复杂多线程环境下使用读写锁,全部日志成功写入了日志文件,由ThreadId和DateTime可以看出是由不同的线程同步写入。

代码二:

   class Program
    {
        static void Main(string[] args)
        {
            #region 简单使用
            //var mutexKey = MutexExample.GetFilePathMutexKey("文件路径");
            //MutexExample.MutexExec(mutexKey, () =>
            //{
            //    Console.WriteLine("需要进程同步执行的代码");
            //});
            #endregion

            #region 测试代码
            //D:\Winform\多线程\多線程_互斥信號量\bin\Debug\TEST.LOG
            var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.log").ToUpper();
            var mutexKey = MutexExample.GetFilePathMutexKey(filePath);

            //同时开启N个写入线程
            Parallel.For(0, LogCount, e =>
            {
                //没使用互斥锁操作写入,大量写入错误;FileStream包含FileShare的构造函数也仅实现了进程内的线程同步,多进程同时写入时也会出错
                //WriteLog(filePath);

                //使用互斥锁操作写入,由于同一时间仅有一个线程操作,所以不会出错
                MutexExample.MutexExec(mutexKey, () =>
                {
                    WriteLog(filePath);
                });
            });

            Console.WriteLine(string.Format("Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
            Console.Read();
            #endregion
        }


        /// <summary>
        /// C#互斥量使用示例代码
        /// </summary>
        /// <remarks>已在经过测试并上线运行,可直接使用</remarks>
        public static class MutexExample
        {
            /// <summary>
            /// 进程间同步执行的简单例子
            /// </summary>
            /// <param name="action">同步处理代码</param>
            /// <param name="mutexKey">操作系统级的同步键
            /// (如果将 name 指定为 null 或空字符串,则创建一个局部互斥体。 
            /// 如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。 
            /// 如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见。 
            /// 如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。)</param>
            /// <remarks>不重试且不考虑异常情况处理的简单例子</remarks>
            [Obsolete(error: false, message: "请使用MutexExec")]
            public static void MutexExecEasy(string mutexKey, Action action)
            {
                //声明一个已命名的互斥体,实现进程间同步;该命名互斥体不存在则自动创建,已存在则直接获取
                using (Mutex mut = new Mutex(false, mutexKey))
                {
                    try
                    {
                        //上锁,其他线程需等待释放锁之后才能执行处理;若其他线程已经上锁或优先上锁,则先等待其他线程执行完毕
                        mut.WaitOne();
                        //执行处理代码(在调用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的时间段里,只有一个线程处理,其他线程都得等待释放锁后才能执行该代码段)
                        action();
                    }
                    finally
                    {
                        //释放锁,让其他进程(或线程)得以继续执行
                        mut.ReleaseMutex();
                    }
                }
            }


            /// <summary>
            /// 获取文件名对应的进程同步键
            /// </summary>
            /// <param name="filePath">文件路径(请注意大小写及空格)</param>
            /// <returns>进程同步键(互斥体名称)</returns>
            public static string GetFilePathMutexKey(string filePath)
            {
                //生成文件对应的同步键,可自定义格式(互斥体名称对特殊字符支持不友好,遂转换为BASE64格式字符串)
                var fileKey = Convert.ToBase64String(Encoding.Default.GetBytes(string.Format(@"FILE\{0}", filePath)));
                //转换为操作系统级的同步键
                var mutexKey = string.Format(@"Global\{0}", fileKey);
                return mutexKey;
            }

            /// <summary>
            /// 进程间同步执行
            /// </summary>
            /// <param name="mutexKey">操作系统级的同步键
            /// (如果将 name 指定为 null 或空字符串,则创建一个局部互斥体。 
            /// 如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。 
            /// 如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见。 
            /// 如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。)</param>
            /// <param name="action">同步处理操作</param>
            public static void MutexExec(string mutexKey, Action action)
            {
                MutexExec(mutexKey: mutexKey, action: action, recursive: false);
            }

            /// <summary>
            /// 进程间同步执行
            /// </summary>
            /// <param name="mutexKey">操作系统级的同步键
            /// (如果将 name 指定为 null 或空字符串,则创建一个局部互斥体。 
            /// 如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。 
            /// 如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见。 
            /// 如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。)</param>
            /// <param name="action">同步处理操作</param>
            /// <param name="recursive">指示当前调用是否为递归处理,递归处理时检测到异常则抛出异常,避免进入无限递归</param>
            private static void MutexExec(string mutexKey, Action action, bool recursive)
            {
                //声明一个已命名的互斥体,实现进程间同步;该命名互斥体不存在则自动创建,已存在则直接获取
                //initiallyOwned: false:默认当前线程并不拥有已存在互斥体的所属权,即默认本线程并非为首次创建该命名互斥体的线程
                //注意:并发声明同名的命名互斥体时,若间隔时间过短,则可能同时声明了多个名称相同的互斥体,并且同名的多个互斥体之间并不同步,高并发用户请另行处理
                using (Mutex mut = new Mutex(initiallyOwned: false, name: mutexKey))
                {
                    try
                    {
                        //上锁,其他线程需等待释放锁之后才能执行处理;若其他线程已经上锁或优先上锁,则先等待其他线程执行完毕
                        mut.WaitOne();
                        //执行处理代码(在调用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的时间段里,只有一个线程处理,其他线程都得等待释放锁后才能执行该代码段)
                        action();
                    }
                    //当其他进程已上锁且没有正常释放互斥锁时(譬如进程忽然关闭或退出),则会抛出AbandonedMutexException异常
                    catch (AbandonedMutexException ex)
                    {
                        //避免进入无限递归
                        if (recursive)
                            throw ex;

                        //非递归调用,由其他进程抛出互斥锁解锁异常时,重试执行
                        MutexExec(mutexKey: mutexKey, action: action, recursive: true);
                    }
                    finally
                    {
                        //释放锁,让其他进程(或线程)得以继续执行
                        mut.ReleaseMutex();
                    }
                }
            }
        }


        #region 测试写文件的代码
        static int LogCount = 500;
        static int WritedCount = 0;
        static int FailedCount = 0;
        static void WriteLog(string logFilePath)
        {
            try
            {
                var now = DateTime.Now;
                var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
                File.AppendAllText(logFilePath, logContent);
                WritedCount++;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                FailedCount++;
            }
        }
        #endregion
    }

运行结果:

 

c/s架构winform开发框架

版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:C# LINQ使用案例参考
下一篇:C#直接使用DllImport外部Dll的方法
评论列表

发表评论

评论内容
昵称:
关联文章

C#线使用ReaderWriterLockSlim同步写入文件
C#.Net使用线池(ThreadPool)与专用线(Thread)
C# 线入门 - 开发实例
C#异步编程(线
C#线处理个队列的数据(交叉线访问及Invoke方法使用)
C#.NET7 线播放mp3/wav音频|winmm.dll winapi播放音乐文件
C#,Asp.Net线断点续传下载
C# INI本地文件工具类IniFileTool.cs
C#异步操作等待窗体,异步线处理数据通用界面(frmThreadOperating)
C#线异步处理数据通用界面窗体(frmThreadOperating)
DataLock - 数据、单据号码、并发C#源码)
尝试释放正在使用的RCW,活动线或其他线上正在使用该RCW
.NET5 HttpClient线并发请求阻塞“发生一个或个错误”解决方案
C#.Net前台线与后台线的区别
编译器错误消息: CS0016: 未能写入输出文件c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root
C#序列化/反序列化对象.将对象写入文件,从文件读取对象.
C# 设置文件目录只属性,取消只属性
SQL死的分析与解决(转)
C# OOP编程 模拟做早餐探索同步异步机制
C#操作Excel合并个Excel文件

热门标签
软件著作权登记证书 .NET .NET Reactor .NET5 .NET6 .NET7 .NET8 .NET9 .NETFramework AI编程 APP AspNetCore AuthV3 Auth-软件授权注册系统 Axios B/S B/S开发框架 B/S框架 BSFramework Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFramework.License CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 CSFrameworkV6.1 CSFrameworkV6旗舰版 DAL数据访问层 Database datalock DbFramework DeepSeek Demo教学 Demo实例 Demo下载 DevExpress教程 Docker Desktop DOM ECS服务器 EFCore EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS MySql NavBarControl NETCore Node.JS NPM OMS Oracle资料 ORM PaaS POS Promise API PSD QMS RedGet Redis RSA SAP Schema SEO SEO文章 SQL SQLConnector SQLite SqlServer Swagger TMS系统 Token令牌 VS2022 VSCode VS升级 VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 XtraReport 安装环境 版本区别 报表 备份还原 踩坑日记 操作手册 达梦数据库 代码生成器 迭代开发记录 功能介绍 官方软件下载 国际化 基础资料窗体 架构设计 角色权限 开发sce 开发工具 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 框架升级 毛衫行业ERP 秘钥 密钥 权限设计 软件报价 软件测试报告 软件加壳 软件简介 软件开发框架 软件开发平台 软件开发文档 软件授权 软件授权注册系统 软件体系架构 软件下载 软件著作权登记证书 软著证书 三层架构 设计模式 生成代码 实用小技巧 视频下载 收钱音箱 数据锁 数据同步 塑木地板行业ERP 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 新功能 信创 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序
联系我们
联系电话:13923396219(微信同号)
电子邮箱:23404761@qq.com
站长微信二维码
微信二维码