异步写入多个线程的文件c#

时间:2010-08-17 23:31:22

标签: c# multithreading file-io asynchronous io

这是我的情况。我想在我的应用程序中尽可能高效地写入文件系统。该应用程序是多线程的,每个线程都可以写入同一个文件。有没有一种方法可以从每个线程异步写入文件,而不会让不同线程中的写入一起发生,可以这么说呢?

我正在使用C#和.NET 3.5,我也安装了Reactive Extensions。

7 个答案:

答案 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);
            }
        }
    }
}