2016年9月15日木曜日

【C#】複数スレッドから同一ファイル書き込み


■結論
自前でlockする方法が安全。
lockした場合でもエディタ等で開くと例外が発生するので注意。 TextWriter.Synchronized()の有用性は不明。 以下に試したコードを記載する。

当然だが、log4net等のライブラリを使うのがベスト。どうしても使えない事情がある場合のみ自前でlockを行うこと。

■lockを使用
  • コード
using System;
using System.Text;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThreadFileWrite
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"c:\tmp\log.txt";
            string errPath = @"c:\tmp\err_";
            object lockObj = new object();     // 状況次第でインスタンス変数にして、staticにすること。

            try
            {
                Parallel.For(0, 10, (name) =>
                {
                    try
                    {
                        for (int i = 0; i < 10000; i++)
                            lock (lockObj)
                                using (StreamWriter sw = new StreamWriter(path, true, Encoding.GetEncoding("Shift_JIS")))
                                    sw.WriteLine("0x" + Thread.CurrentThread.ManagedThreadId.ToString("X8") + ":" + i);
                    }
                    catch (Exception ex)
                    {
                        File.WriteAllText(errPath + Path.GetRandomFileName(), ex.Message + Environment.NewLine + ex.StackTrace);
                    }
                });
            }
            catch (Exception ex)
            {
                File.WriteAllText(errPath + Path.GetRandomFileName() + ".log", ex.Message + Environment.NewLine + ex.StackTrace);
            }
        }
    }
}

  • 結果 10万行全て正しい。

■TextWriter.Synchronized + ReadWrite (稀にファイル書き込みに抜けがある)
  • コード
using System;
using System.Text;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThreadFileWrite
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"c:\tmp\log.txt";
            string errPath = @"c:\tmp\err_";

            try
            {
                Parallel.For(0, 10, (name) =>
                {
                    try
                    {
                        for (int i = 0; i < 10000; i++)
                            using (FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                            using (StreamWriter sw = new StreamWriter(fs, Encoding.GetEncoding("Shift_JIS")))
                            using (TextWriter tw = TextWriter.Synchronized(sw))
                                tw.WriteLine(name + "(" + Thread.CurrentThread.ManagedThreadId.ToString("X8") + "):" + i);
                    }
                    catch (Exception ex)
                    {
                        File.WriteAllText(errPath + Path.GetRandomFileName(), ex.Message + Environment.NewLine + ex.StackTrace);
                    }
                });
            }
            catch (Exception ex)
            {
                File.WriteAllText(errPath + Path.GetRandomFileName() + ".log", ex.Message + Environment.NewLine + ex.StackTrace);
            }
        }
    }
}

  • 結果(抜粋)
以下のように2633が抜けている。稀に発生する。

0(00000009):2632
5(0000000A):12

5(0000000A):13
0(00000009):2634

■TextWriter.Synchronized + append=true(例外が発生する)

  • コード
using System;
using System.Text;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThreadFileWrite
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"c:\tmp\log.txt";
            string errPath = @"c:\tmp\err_";

            try
            {
                Parallel.For(0, 10, (name) =>
                {
                    try
                    {
                        for (int i = 0; i < 10000; i++)
                            using (StreamWriter sw = new StreamWriter(path, true, Encoding.GetEncoding("Shift_JIS")))
                            using (TextWriter tw = TextWriter.Synchronized(sw))
                                tw.WriteLine(Thread.CurrentThread.Name + ":" + i);
                    }
                    catch (Exception ex)
                    {
                        File.WriteAllText(errPath + Path.GetRandomFileName(), ex.Message + Environment.NewLine + ex.StackTrace);
                    }
                });
            }
            catch (Exception ex)
            {
                File.WriteAllText(errPath + Path.GetRandomFileName() + ".log", ex.Message + Environment.NewLine + ex.StackTrace);
            }
        }
    }
}


  • 結果(抜粋) 別のプロセスで使用されているため、プロセスはファイル 'c:\tmp\log.txt' に アクセスできません。

0 件のコメント:

コメントを投稿