你的位置:首页 > 软件开发 > ASP.net > C#使用读写锁三行代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题

C#使用读写锁三行代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题

发布时间:2016-12-12 09:00:21
在开发程序的过程中,难免少不了写入错误日志这个关键功能。实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件。选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同 ...

C#使用读写锁三行代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题

在开发程序的过程中,难免少不了写入错误日志这个关键功能。实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件。

选择最后一种方法实现的时候,若对文件操作与get='_blank'>线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示“文件正在由另一进程使用,因此该进程无法访问此文件”。

 

这是文件的并发写入问题,就需要用到线程同步。而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使用到的 System.Threading.ReaderWriterLockSlim 便是其中之一。

该类用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。利用这个类,我们就可以避免在同一时间段内多线程同时写入一个文件而导致的并发写入问题。

 

读写锁是以 ReaderWriterLockSlim 对象作为锁管理资源的,不同的 ReaderWriterLockSlim 对象中锁定同一个文件也会被视为不同的锁进行管理,这种差异可能会再次导致文件的并发写入问题,所以 ReaderWriterLockSlim 应尽量定义为只读的静态对象。

 

ReaderWriterLockSlim 有几个关键的方法,本文仅讨论写入锁:

调用 EnterWriteLock 方法 进入写入状态,在调用线程进入锁定状态之前一直处于阻塞状态,因此可能永远都不返回。不使用读写锁,只有部分日志成功写入了日志文件。

 

2.多线程使用读写锁同步写入文件

 1   class Program 2   { 3     static int LogCount = 100; 4     static int WritedCount = 0; 5     static int FailedCount = 0; 6  7     static void Main(string[] args) 8     { 9       //迭代运行写入日志记录10       Parallel.For(0, LogCount, e =>11       {12         WriteLog();13       });14 15       Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));16       Console.Read();17     }18 19     //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入20     static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();21     static void WriteLog()22     {23       try24       {25         //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入26         //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。27         //   从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度28         //   因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常29         LogWriteLock.EnterWriteLock();30 31         var logFilePath = "log.txt";32         var now = DateTime.Now;33         var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());34 35         File.AppendAllText(logFilePath, logContent);36         WritedCount++;37       }38       catch (Exception)39       {40         FailedCount++;41       }42       finally43       {44         //退出写入模式,释放资源占用45         //注意:一次请求对应一次释放46         //   若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放]47         //   若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定]48         LogWriteLock.ExitWriteLock();49       }50     }51   }
使用读写锁,全部日志成功写入了日志文件。

 

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

 1   class Program 2   { 3     static int LogCount = 1000; 4     static int SumLogCount = 0; 5     static int WritedCount = 0; 6     static int FailedCount = 0; 7  8     static void Main(string[] args) 9     { 10       //往线程池里添加一个任务,迭代写入N个日志 11       SumLogCount += LogCount; 12       ThreadPool.QueueUserWorkItem((obj) => 13       { 14         Parallel.For(0, LogCount, e => 15         { 16           WriteLog(); 17         }); 18       }); 19  20       //在新的线程里,添加N个写入日志的任务到线程池 21       SumLogCount += LogCount; 22       var thread1 = new Thread(() => 23       { 24         Parallel.For(0, LogCount, e => 25         { 26           ThreadPool.QueueUserWorkItem((subObj) => 27           { 28             WriteLog(); 29           }); 30         }); 31       }); 32       thread1.IsBackground = false; 33       thread1.Start(); 34  35       //添加N个写入日志的任务到线程池 36       SumLogCount += LogCount; 37       Parallel.For(0, LogCount, e => 38       { 39         ThreadPool.QueueUserWorkItem((obj) => 40         { 41           WriteLog(); 42         }); 43       }); 44  45       //在新的线程里,迭代写入N个日志 46       SumLogCount += LogCount; 47       var thread2 = new Thread(() => 48       { 49         Parallel.For(0, LogCount, e => 50         { 51           WriteLog(); 52         }); 53       }); 54       thread2.IsBackground = false; 55       thread2.Start(); 56  57       //在当前线程里,迭代写入N个日志 58       SumLogCount += LogCount; 59       Parallel.For(0, LogCount, e => 60       { 61         WriteLog(); 62       }); 63  64       Console.WriteLine("Main Thread Processed.\r\n"); 65       while (true) 66       { 67         Console.WriteLine(string.Format("Sum Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", SumLogCount.ToString(), WritedCount.ToString(), FailedCount.ToString())); 68         Console.ReadLine(); 69       } 70     } 71  72     //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 73     static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); 74     static void WriteLog() 75     { 76       try 77       { 78         //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入 79         //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。 80         //   从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度 81         //   因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常 82         LogWriteLock.EnterWriteLock(); 83  84         var logFilePath = "log.txt"; 85         var now = DateTime.Now; 86         var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString()); 87  88         File.AppendAllText(logFilePath, logContent); 89         WritedCount++; 90       } 91       catch (Exception) 92       { 93         FailedCount++; 94       } 95       finally 96       { 97         //退出写入模式,释放资源占用 98         //注意:一次请求对应一次释放 99         //   若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放]100         //   若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定]101         LogWriteLock.ExitWriteLock();102       }103     }104   }

原标题:C#使用读写锁三行代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题

关键词:C#

C#
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

可能感兴趣文章

我的浏览记录