Подтвердить что ты не робот

Запись в файл из нескольких потоков асинхронно С#

Вот моя ситуация. Я хотел бы сделать запись в файловую систему максимально эффективной в моем приложении. Приложение многопоточное, и каждый поток может записывать в один и тот же файл. Есть ли способ, который я могу записать в файл асинхронно из каждого потока без того, чтобы записи в разных потоках болтались вместе, так сказать?

Я использую С# и .NET 3.5, и у меня также установлены Reactive Extensions.

4b9b3361

Ответ 1

Посмотрите на Асинхронный ввод/вывод. Это освободит процессор для продолжения выполнения других задач.
Объедините с ReaderWriterLock, как упомянул @Jack B Nimble

Если

запись в файловую систему как максимально эффективно

Вы имеете в виду максимально быстрое выполнение реального файлового ввода-вывода. Вам будет очень тяжело его ускорить, физически диск просто медленнее. Может SSD?

Ответ 2

Для тех, кто предпочитает код, я использую следующие действия для удаленного ведения журнала из веб-приложений...

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

Надеюсь, это сэкономит вам время.

Ответ 3

То, что я хотел бы сделать, это иметь отдельный рабочий поток (ы), посвященный задаче записи файлов. Когда один из ваших других потоков должен записать некоторые данные, он должен вызвать функцию для добавления данных в ArrayList (или некоторый другой контейнер/класс). Внутри этой функции должен быть оператор блокировки вверху, чтобы предотвратить одновременное выполнение нескольких потоков. После добавления ссылки на ArrayList он возвращается и продолжает работу по дому. Есть несколько способов обработки потока (ов) письма. Вероятно, самое простое - просто поместить его в бесконечный цикл с оператором sleep в конце, чтобы он не перегружал ваши процессоры. Другой способ - использовать потоковые примитивы и переходить в состояние ожидания, когда больше нет данных для записи. Этот метод подразумевает, что вам нужно будет активировать поток с помощью метода, подобного ManualResetEvent.Set.

Существует много разных способов считывания и записи файлов в .NET. Я написал тестовую программу и опубликовал результаты в своем блоге:

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

Я бы порекомендовал использовать методы Windows ReadFile и WriteFile, если вам нужна производительность. Избегайте любых асинхронных методов, так как мои результаты тестов показывают, что вы получаете лучшую производительность с синхронными методами ввода-вывода.

Ответ 4

В то время как блокировки на основе потоков могут решить эту проблему, существует способ, который работает по потокам, но, вероятно, лучше всего использовать, когда у вас есть несколько процессов, записывающих в конец одного файла.

Чтобы получить это поведение во всех процессах (или в потоках), укажите, что вы хотите, чтобы атомарная запись append в операционную систему была создана при создании файлов OS. Это делается путем указания O_APPEND под Posix (Linux, Unix) и FILE_APPEND_DATA под Windows.

В С# вы не вызываете системные вызовы OS 'open' или 'CreateFile' напрямую, но есть способы получить этот результат.

Я спросил, как это сделать под Windows некоторое время назад, и получил два хороших ответа здесь: Как я могу сделать атомарную запись/добавление в С# или как получить файлы открыт с флагом FILE_APPEND_DATA?

В принципе, вы можете использовать FileStream() или PInvoke, я бы предложил FileStream() над PInvoke по понятным причинам.

Вы можете использовать аргументы конструктора для FileStream() для указания асинхронного ввода-вывода файлов в дополнение к флагу FileSystemRights.AppendData, который должен предоставить вам как асинхронные операции ввода-вывода, так и атомарную запись в файл.

Предупреждение. Некоторые операционные системы имеют ограничения на максимальное количество байтов, которые могут быть атомарно записаны таким образом, и превышение этого порогового значения устранит обезвреживание операционной системы.

Из-за этой последней полученной информации я бы рекомендовал оставаться с управлением конфликтами стиля lock() при попытке решить вашу проблему в рамках одного процесса.

Ответ 5

Используйте Reader/Writer блокирует доступ к файловому потоку.

Ответ 6

Сохранить в журнале с очередью и несколькими потоками (пример .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

Ответ 7

Вы можете использовать события для регистратора:

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace EventLogger
{
    class Program
    {
        static void Main(string[] args)
        {                   
            // Event handler
            LogData ld = new LogData();
            // Logger
            Logger lo = new Logger();                    
            // Subscribe to event
            ld.MyEvent += lo.OnMyEvent;     
            // Thread loop
            int cnt = 1;
            while(cnt < 5){         
                Thread t = new Thread(() => RunMe(cnt, ld));
                t.Start();
                cnt++;
            }
            Console.WriteLine("While end");
        }

        // Thread worker
        public static void RunMe(int cnt, LogData ld){
            int nr = 0;
            while(true){
                nr++;
                // Add user and fire event
                ld.AddToLog(new User(){Name = "Log to file Thread" + cnt + " Count " + nr, Email = "[email protected]"});
                Thread.Sleep(1);
            }
        }
    }

    class LogData
    {
        public delegate void MyEventHandler(object o, User u);
        public event MyEventHandler MyEvent;

        protected virtual void OnEvent(User u)
        {
            if(MyEvent != null){
                MyEvent(this, u);
            }

        }

        // Wywołaj
        public void AddToLog(User u){
            Console.WriteLine("Add to log.");

            // Odpal event
            OnEvent(u);

            Console.WriteLine("Added.");
        }
    }

    class User
    {
        public string Name = "";
        public string Email =  "";
    }

    class Logger
    {
        // Catch event
        public void OnMyEvent(object o, User u){
            try{
                Console.WriteLine("Added to file log! " + u.Name + " " + u.Email);
                File.AppendAllText(@"event.log", "Added to file log! " + u.Name + " " + u.Email+"\r\n");
            }catch(Exception e){
                Console.WriteLine("Error file log " + e);
            }
        }
    }
}