.Net 併發寫入文件的多種方式

一、簡介html

本文主要演示平常開發中利用多線程寫入文件存在的問題,以及解決方案,本文使用最經常使用的日誌案例!安全

 

二、使用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不容許多個線程同時操做同一個文件,因此,拋異常.因此必須解決這個問題。測試

 

三、利用ReadWriterSlim解決多線程徵用文件問題優化

關於ReadWriterSlim的使用,在本人的這篇隨筆中已介紹,在其基礎上,對SynchronizedCache類稍稍改造,造成一個SynchronizedFile類,對相關操做代碼進行線程安全處理,即能解決當前的問題,代碼以下:spa

   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();
            }
        }
    }

調用代碼以下所示:pwa

        static string _filePath = @"C:\Users\zhengchao\Desktop\測試文件.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()}\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的利用率.

 

四、優化代碼

        static string _filePath = @"C:\Users\zhengchao\Desktop\測試文件.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()}\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利用率.暫時還沒找到多線程寫入一個文件,不須要加讀鎖的方法,若是有,請告知.

相關文章
相關標籤/搜索