C#でfile_put_contentsしたかった

  • 2
    いいね
  • 6
    コメント

AzureのAppService上でファイルを追記したいだけの人生だった。

    using System.IO;

    /**
    * file_put_contents(FILE_APPEND)
    * @param string ファイル名
    * @param string 書き込み内容
    */
    public static void file_put_contents(string FileName, string SaveText){
        // 実際はフォーマットとか追加があるけど本質的な違いは無いので略
        File.AppendAllText(FileName, SaveText);
    }

という素晴らしく手抜きな関数を使っていたところ、リクエストが重なったときに競合であっさり死亡。

    // わりと頻繁に発生
    System.IO.IOException: The process cannot access the file 'hoge.txt' because it is being used by another process.

PHP脳はマルチスレッドに弱いのだ。

で、これをどうにかしたいと思ったのだが、キューに投げておけば後で勝手に書き込んでくれる、みたいな機能はどうやら用意されていないみたいだ。
ちなみにAppendAllTextのサンプルは、そもそもcatchすらしてないものが大半だった。

しかたないのでTextWriter.Synchronized()を使うことにする。

    public static void file_put_contents(string FileName, string SaveText){
        using(StreamWriter writer = new StreamWriter(FileName, true)){
            // まだ何も書いてない
        }
    }
    // 希に発生
    System.IO.IOException: The process cannot access the file 'hoge.txt' because it is being used by another process.

どうやらファイルをStreamWriterで開くだけで死ぬらしい。
Synchronizedとかする以前の問題だった。
ドキュメントには「ファイル名、ディレクトリ名が正しくないときに出る」としか書いてないのだが、どう見てもロック失敗である。

えっこれどうすんの?
newしただけでExceptionとか聞いてないよ。
WriteをcatchするとかSynchronizedするとかのソリューションはよく見かけるけど、実は解決になっていないということがわかった。

その後もなんか色々調べたりしたんだけどそのあたりは省略して、最終的にできあがったのがこんな。

    public static void file_put_contents(string FileName, string SaveText){
        using(FileStream fs = new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)){
            using(StreamWriter sw = new StreamWriter(fs)){
                using(TextWriter tw = TextWriter.Synchronized(sw)){
                    tw.Write(SaveText);
                    tw.Flush();
                }
            }
        }
    }

例外は発生せず、動作も安定しているように見えるのだが、本当にこれで合ってるのか???