Запись в файл из нескольких потоков асинхронно С#
Вот моя ситуация. Я хотел бы сделать запись в файловую систему максимально эффективной в моем приложении. Приложение многопоточное, и каждый поток может записывать в один и тот же файл. Есть ли способ, который я могу записать в файл асинхронно из каждого потока без того, чтобы записи в разных потоках болтались вместе, так сказать?
Я использую С# и .NET 3.5, и у меня также установлены Reactive Extensions.
Ответы
Ответ 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);
}
}
}
}