web-dev-qa-db-ja.com

複数のスレッドから非同期にファイルに書き込むC#

これが私の状況です。私のアプリケーションでファイルシステムへの書き込みを可能な限り効率的にしたいと思います。アプリはマルチスレッドであり、各スレッドは同じファイルに書き込むことができます。いわば、異なるスレッドへの書き込みをまとめて行うことなく、各スレッドから非同期にファイルに書き込む方法はありますか?

C#と.NET 3.5を使用していますが、Reactive Extensionsもインストールしています。

37
Jeffrey Cameron

非同期I/O をご覧ください。これにより、CPUが解放され、他のタスクを続行できます。
@ Jack B Nimbleが述べたように ReaderWriterLock と組み合わせます

可能な限り効率的なファイルシステムへの書き込み

実際のファイルI/Oを可能な限り高速にすることを意味します。高速化に苦労することになり、ディスクは物理的に遅くなります。たぶんSSDの?

14
µBio

コードを好む人のために、私は以下を使用してウェブアプリからリモートロギングをしています...

public static class LoggingExtensions
{
    static ReaderWriterLock locker = new ReaderWriterLock();
    public static void WriteDebug(this string text)
    {
        try
        {
            locker.AcquireWriterLock(int.MaxValue); //You might wanna change timeout value 
            System.IO.File.AppendAllLines(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", ""), "debug.txt"), new[] { text });
        }
        finally
        {
            locker.ReleaseWriterLock();
        }
    }
}

これで時間を節約できることを願っています

11

私がやることは、ファイルを書き込むタスク専用の個別のワーカースレッドを用意することです。他のスレッドの1つがデータを書き出す必要がある場合、関数を呼び出してデータをArrayList(または他のコンテナー/クラス)に追加する必要があります。この関数内では、複数のスレッドが同時に実行されるのを防ぐために、上部にロックステートメントが必要です。 ArrayListに参照を追加した後、戻り、雑用を続けます。書き込みスレッドを処理するには、いくつかの方法があります。おそらく最も単純な方法は、CPUを噛まないように、最後にsleepステートメントを使用して単純に無限ループに入れることです。もう1つの方法は、スレッドプリミティブを使用して、書き込むデータがなくなったときに待機状態にすることです。このメソッドは、ManualResetEvent.Setメソッドのようなものでスレッドをアクティブ化する必要があることを意味します。

.NETでファイルの読み取りと書き込みを行うには、さまざまな方法があります。ベンチマークプログラムを作成し、ブログで結果を示しています。

http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp

パフォーマンスが必要な場合は、WindowsのReadFileメソッドとWriteFileメソッドを使用することをお勧めします。ベンチマークの結果から、同期I/Oメソッドを使用するとパフォーマンスが向上することが示されているため、非同期メソッドは避けてください。

7
Bob Bryan

スレッドベースのロックはこれを解決できますが、スレッド間で機能する方法がありますが、単一のファイルの最後に複数のプロセスがを書き込む場合におそらく最もよく使用されます

プロセス(またはスレッド)全体でこの動作を実現するには、OSファイルハンドルの作成時にオペレーティングシステムへのアトミックアペンド書き込みを指定します。これを行うには、Posix(Linux、Unix)でO_APPENDを指定し、WindowsでFILE_APPEND_DATAを指定します。

C#では、OSを「オープン」または「CreateFile」システムコールを直接呼び出しませんが、この結果を取得する方法があります。

私は少し前にWindowsでこれを行う方法を尋ね、ここで2つの良い回答を得ました: C#でアトミックな書き込み/追加を行う方法、またはFILE_APPEND_DATAフラグでファイルを開く方法は?

基本的に、FileStream()またはPInvokeを使用できます。明白な理由から、PInvokeよりFileStream()をお勧めします。

FileStream()のコンストラクター引数を使用して、FileSystemRights.AppendDataフラグに加えて非同期ファイルI/Oを指定できます。これにより、非同期I/Oとアトミックなファイルへの書き込みの両方が可能になります。

警告:一部のOSには、この方法でアトミックに書き込むことができる最大バイト数に制限があり、そのしきい値を超えると、OSのアトミック性が失われます。

この最後の落とし穴があるため、単一のプロセスで問題に対処しようとするときは、lock()スタイルの競合管理を続けることをお勧めします。

6
Cameron

Reader/Writer ロックを使用して、ファイルストリームにアクセスします。

4
Jack B Nimble

キューおよび複数のスレッドでログに保存(.Net Core 2.2 Linuxサンプル-テスト済み)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
// add
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.IO;
using System.Timers;

namespace LogToFile
{
    class Program
    {
        public static Logger logger = new Logger("debug.log");

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            logger.add("[001][LOGGER STARTING]");

            Thread t0 = new Thread(() => DoWork("t0"));
            t0.Start();

            Thread t1 = new Thread(() => DoWork("t1"));
            t1.Start();

            Thread t2 = new Thread(() => DoWork("t2"));
            t2.Start();

            Thread ts = new Thread(() => SaveWork());
            ts.Start();
        }

        public static void DoWork(string nr){
            while(true){
                logger.add("Hello from worker .... number " + nr);
                Thread.Sleep(300);
            }
        }

        public static void SaveWork(){
            while(true){
                logger.saveNow();
                Thread.Sleep(50);
            }
        }
    }

    class Logger
    {
        // Queue import: 
        // using System.Collections
        public Queue logs = new Queue();
        public string path = "debug.log";

        public Logger(string path){
            this.path = path;
        }

        public void add(string t){
            this.logs.Enqueue("[" + currTime() +"] " + t);
        }

        public void saveNow(){
            if(this.logs.Count > 0){
                // Get from queue
                string err = (string) this.logs.Dequeue();
                // Save to logs
                saveToFile(err, this.path);
            }
        }

        public bool saveToFile(string text, string path)
        {
            try{
                // string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                // text = text + Environment.NewLine;
                using (StreamWriter sw = File.AppendText(path))
                {
                    sw.WriteLine(text);
                    sw.Close();
                }
            }catch(Exception e){
                // return to queue
                this.logs.Enqueue(text + "[SAVE_ERR]");
                return false;
            }
            return true;
        }

        public String currTime(){
            DateTime d = DateTime.UtcNow.ToLocalTime();
            return d.ToString("yyyy-MM-dd hh:mm:ss");
        }
    }
}

コンパイル(保存先:LogToFile/Program.cs):

dotnet new console -o LogToFile
cd LogToFile
dotnet build
dotnet run

CTRL + Cアプリを停止してログファイルを表示

cat debug.log
1
Maxiu