这是我的情况。我希望在我的应用程序中尽可能高效地向文件系统写入。该应用程序是多线程的,每个线程都可能写入同一文件。有没有办法让每个线程异步地写入文件,而不会使得不同线程之间发生冲突呢?
我正在使用C#和.NET 3.5,并且已安装了反应式扩展。
这是我的情况。我希望在我的应用程序中尽可能高效地向文件系统写入。该应用程序是多线程的,每个线程都可能写入同一文件。有没有办法让每个线程异步地写入文件,而不会使得不同线程之间发生冲突呢?
我正在使用C#和.NET 3.5,并且已安装了反应式扩展。
对于那些喜欢编程的人,我正在使用以下代码从Web应用程序进行远程记录...
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();
}
}
}
希望这可以节省您一些时间
请查看异步 I/O。这将释放 CPU 继续执行其他任务。
结合ReaderWriterLock,就像 @Jack B Nimble 提到的那样。
如果您指的是希望将文件系统写入尽可能地提高效率,则很难让实际的文件 I/O 更快,因为磁盘速度本身较慢。也许可以考虑使用固态硬盘(SSD)?
http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp
我建议使用Windows的ReadFile和WriteFile方法,如果你需要更好的性能。避免使用任何异步方法,因为我的基准测试结果表明,同步I/O方法可以获得更好的性能。虽然基于线程的锁可以解决这个问题,但是有一种跨线程工作的方式,当您有多个进程写入单个文件末尾时,最好使用它。
要在进程(或线程)之间实现此行为,请在创建操作系统文件句柄时指定您想要原子追加写入。这可以通过在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,出于明显的原因,我建议使用FileStream()而不是PInvoke。
您可以使用构造函数参数向FileStream()指定异步文件I/O,除了FileSystemRights.AppendData标志外,这应该会给您既有异步I/O又有对文件的原子追加写入。
警告:某些操作系统对以这种方式原子写入的最大字节数有限制,超过该阈值将取消操作系统对原子性的承诺。
由于这个问题的最后一个陷阱,当尝试在单个进程中解决问题时,我建议使用lock()样式的争用管理。
使用队列和多线程将日志保存 (已在 .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
你可以使用事件来记录器:
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);
}
}
}
}