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

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

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

6 ответов


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

Если

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

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


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

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

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

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

Боб Брайан MCSD


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

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

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


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

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

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

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

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

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

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

из-за этого последнего gotcha, я бы рекомендовал остаться с lock() style contention management при попытке решить вашу проблему в рамках одного процесса.


использовать Читатель / Писатель блокировки для доступа к файловому потоку.


это форма, которая запускает число потоков, определенных в том же файле txt. Он был создан в .net 3.5 и winforms, я думаю, что вы можете адаптироваться к вашему использованию. Создайте форму и добавьте кнопку! Надеюсь, это поможет!

public partial class Form1 : Form
{
    private const int NumberThreads = 39; //Total Threads
    private const int NumberLines = 999; // Lines per Thread
    private string _filePath = Path.Combine(Directory.GetCurrentDirectory(), "debug.txt");

    private static readonly object _objectLock = new object();

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (File.Exists(_filePath))
            File.Delete(_filePath); //delete file

        if (!File.Exists(_filePath))
            using (var sw = File.CreateText(_filePath)) //create file
                sw.WriteLine("TEST MULTITHREAD WRITTER"); //write header

        //create threads
        for (var i = 0; i <= NumberThreads; i++)
        {
            var thread = new Thread(ThreadWrite) { IsBackground = true };
            thread.Start(); //start threads
        }
    }

    private void ThreadWrite()
    {
        for (var i = 0; i <= NumberLines; i++)
        {
            //append a date in line
            AppendLine(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")); 
            //sleeps a rand interval between 1 and 10 secs
            Thread.Sleep(new Random().Next(1, 10));
        }
    }

    private void AppendLine(string text)
    {
        lock (_objectLock)
        {
            var line = text;
            using (var sw = new StreamWriter(_filePath, true))
            {
                //write line to file
                sw.WriteLine(line);
                //close file
                sw.Close();
            }
        }
    }
}