web-dev-qa-db-fra.com

Écrire dans un fichier à partir de plusieurs threads c # de manière asynchrone

Voici ma situation. Je souhaite rendre l'écriture dans le système de fichiers aussi efficace que possible dans ma candidature. L'application est multi-thread et chaque thread peut éventuellement écrire dans le même fichier. Existe-t-il un moyen d'écrire dans le fichier de manière asynchrone à partir de chaque thread sans que les écritures dans les différents threads ne frappent ensemble, pour ainsi dire?

J'utilise C # et .NET 3.5, et j'ai également les extensions réactives installées.

37
Jeffrey Cameron

Jetez un œil à E/S asynchrones . Cela libérera le processeur pour continuer d'autres tâches.
Combinez avec ReaderWriterLock comme mentionné @Jack B Nimble

Si par

écriture dans le système de fichiers aussi efficace que possible

vous voulez dire que les E/S du fichier réel soient aussi rapides que possible, vous aurez beaucoup de mal à l'accélérer beaucoup, le disque est simplement plus lent physiquement. Peut-être des SSD?

14
µBio

Pour ceux qui préfèrent le code, j'utilise ce qui suit pour effectuer la journalisation à distance à partir d'applications 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();
        }
    }
}

J'espère que cela vous fait gagner du temps

11
Matas Vaitkevicius

Ce que je ferais, c'est d'avoir des threads de travail séparés dédiés à la tâche d'écriture des fichiers. Lorsque l'un de vos autres threads doit écrire des données, il doit appeler une fonction pour ajouter les données à une liste de tableaux (ou à un autre conteneur/classe). À l'intérieur de cette fonction, il devrait y avoir une instruction de verrouillage en haut pour empêcher plus d'un thread de s'exécuter simultanément. Après avoir ajouté la référence à ArrayList, il revient et continue ses tâches. Il existe plusieurs façons de gérer le (s) fil (s) d'écriture. Le plus simple est probablement de simplement le mettre dans une boucle infinie avec une instruction de sommeil à la fin afin qu'il ne mâche pas vos processeurs. Une autre façon consiste à utiliser des primitives de threads et à passer dans un état d'attente lorsqu'il n'y a plus de données à écrire. Cette méthode implique que vous devez activer le thread avec quelque chose comme la méthode ManualResetEvent.Set.

Il existe de nombreuses façons différentes de lire et d'écrire des fichiers dans .NET. J'ai écrit un programme de référence et donne les résultats dans mon blog:

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

Je recommanderais d'utiliser les méthodes Windows ReadFile et WriteFile si vous avez besoin de performances. Évitez les méthodes asynchrones, car mes résultats de test montrent que vous obtenez de meilleures performances avec les méthodes d'E/S synchrones.

7
Bob Bryan

Bien que les verrous basés sur les threads puissent résoudre ce problème, il existe une méthode qui fonctionne entre les threads, mais il est probablement préférable de l'utiliser lorsque plusieurs processus écrivent à la fin d'un seul fichier .

Pour obtenir ce comportement entre les processus (ou les threads également), spécifiez que vous voulez que les écritures atomiques d'ajout sur le système d'exploitation lorsque les descripteurs de fichiers du système d'exploitation sont créés. Pour ce faire, spécifiez O_APPEND sous Posix (Linux, Unix) et FILE_APPEND_DATA sous Windows.

En C #, vous n'appelez pas directement le système d'exploitation ou les appels système "CreateFile", mais il existe des moyens d'obtenir ce résultat.

J'ai demandé comment faire cela sous Windows il y a quelque temps, et j'ai obtenu deux bonnes réponses ici: Comment puis-je faire une écriture atomique/ajout en C #, ou comment obtenir des fichiers ouverts avec l'indicateur FILE_APPEND_DATA?

Fondamentalement, vous pouvez utiliser FileStream () ou PInvoke, je suggérerais FileStream () sur PInvoke pour des raisons évidentes.

Vous pouvez utiliser des arguments de constructeur pour FileStream () pour spécifier les E/S de fichiers asynchrones en plus de l'indicateur FileSystemRights.AppendData, qui devrait vous donner à la fois des E/S asynchrones et des écritures d'ajout atomiques dans un fichier.

Avertissement: certains systèmes d'exploitation ont des limites sur le nombre maximal d'octets qui peuvent être écrits de manière atomique de cette façon, et le dépassement de ce seuil supprimera la promesse du système d'exploitation de l'atomicité.

En raison de ce dernier problème, je recommanderais de rester avec la gestion des conflits de style lock () lorsque vous essayez de résoudre votre problème dans un seul processus.

6
Cameron

Utilisez Reader/Writer verrous pour accéder au flux de fichiers.

4
Jack B Nimble

Enregistrer pour vous connecter avec Queue et plusieurs threads (exemple .Net Core 2.2 linux - testé)

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

Compiler (enregistrer dans: LogToFile/Program.cs):

dotnet new console -o LogToFile
cd LogToFile
dotnet build
dotnet run

Arrêtez l'application CTRL + C et consultez le fichier journal

cat debug.log
1
Maxiu