这是我的情况。我想在我的应用程序中尽可能高效地写入文件系统。该应用程序是多线程的,每个线程都可以写入同一个文件。有没有一种方法可以从每个线程异步写入文件,而不会让不同线程中的写入一起发生,可以这么说呢?
我正在使用C#和.NET 3.5,我也安装了Reactive Extensions。
答案 0 :(得分:14)
看看Asynchronous I/O。这将释放cpu以继续执行其他任务。
结合ReaderWriterLock作为@Jack B Nimble提到了
如果通过
写入文件系统 尽可能高效
你的意思是尽可能快地制作实际的文件I / O,你将很难加快速度,磁盘只是速度慢。 SSD也许?
答案 1 :(得分:8)
对于那些喜欢代码的人,我使用以下来从网络应用程序进行远程登录...
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();
}
}
}
希望这能节省你一些时间
答案 2 :(得分:7)
我要做的是有单独的工作线程专门用于编写文件的任务。当你的其他一个线程需要写出一些数据时,它应该调用一个函数将数据添加到ArrayList(或其他一些容器/类)。在这个函数中,顶部应该有一个lock语句,以防止同时执行多个线程。在添加对ArrayList的引用之后,它返回并继续其杂务。有几种方法可以处理写入线程。可能最简单的是简单地将它放入一个带有睡眠语句的无限循环中,这样它就不会咀嚼你的cpu。另一种方法是使用线程原语,并在没有更多数据要写出时进入等待状态。此方法意味着您必须使用类似ManualResetEvent.Set方法的方法激活线程。
有许多不同的方法可以在.NET中读取和写出文件。我写了一个基准程序,并在我的博客中提供结果:
http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp
如果您需要性能,我建议使用Windows ReadFile和WriteFile方法。避免使用任何异步方法,因为我的基准测试结果表明使用同步I / O方法可以获得更好的性能。
答案 3 :(得分:5)
虽然基于线程的锁可以解决这个问题,但有一种方法可以跨线程工作,但是当你有多个进程将写入单个文件的末尾时,最好使用它。
要跨进程(或线程)获取此行为,请指定在创建OS文件句柄时希望对操作系统进行原子追加写入。 这是通过在Posix(Linux,Unix)下指定O_APPEND和在Windows下指定FILE_APPEND_DATA来完成的。
在C#中,你不直接调用操作系统'open'或'CreateFile'系统调用,但有办法获得这个结果。
我刚刚问过如何在Windows下执行此操作,并在此处获得了两个好的答案:How can I do an atomic write/append in C#, or how do I get files opened with the FILE_APPEND_DATA flag?
基本上,您可以使用FileStream()或PInvoke,我建议使用PInvoke上的FileStream(),原因很明显。
除了FileSystemRights.AppendData标志之外,您还可以使用FileStream()的构造函数参数来指定异步文件I / O,该标志应该为您提供异步I / O和原子追加写入文件。
警告:某些操作系统对以这种方式原子写入的最大字节数有限制,超过该阈值将删除操作系统的原子性承诺。
由于最后的问题,我建议在尝试在单个进程中解决问题时保持lock()样式争用管理。
答案 4 :(得分:4)
使用Reader / Writer锁来访问文件流。
答案 5 :(得分:1)
保存到具有队列和多个线程的日志(.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
答案 6 :(得分:0)
您可以将事件用于记录器:
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 = "em@em.xx"});
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);
}
}
}
}