3 Stimmen

Thread anhalten, bis genügend Speicher verfügbar ist

Umgebung: .net 4.0

Ich habe eine Aufgabe, die XML-Dateien mit einem XSLT-Stylesheet transformiert, hier ist mein Code

public string TransformFileIntoTempFile(string xsltPath, 
    string xmlPath)
{
    var transform = new MvpXslTransform();
    transform.Load(xsltPath, new XsltSettings(true, false), 
        new XmlUrlResolver());

    string tempPath = Path.GetTempFileName();

    using (var writer = new StreamWriter(tempPath))
    {
        using (XmlReader reader = XmlReader.Create(xmlPath))
        {
            transform.Transform(new XmlInput(reader), null, 
                new XmlOutput(writer));
        }       
    }

    return tempPath;
}

Ich habe X Threads, die diese Aufgabe parallel starten können. Manchmal sind meine Eingabedateien etwa 300 MB groß, manchmal sind es nur ein paar MB.

Mein Problem: Ich bekomme OutOfMemoryException, wenn mein Programm versuchen, einige große XML-Dateien in der gleichen Zeit zu transformieren.

Wie kann ich diese OutOfMemoryEception vermeiden? Meine Idee ist, einen Thread vor der Ausführung der Aufgabe zu stoppen, bis genügend Speicher verfügbar ist, aber ich weiß nicht, wie man das macht. Oder gibt es eine andere Lösung (wie meine Aufgabe in einer separaten Anwendung).

Gracias

2voto

sehe Punkte 346808

Ich empfehle nicht, einen Thread zu blockieren. Im schlimmsten Fall verhungert die Aufgabe, die möglicherweise den benötigten Speicher freigeben könnte, was zu einem Deadlock oder einer sehr schlechten Leistung im Allgemeinen führt.

Ich schlage vor, dass Sie stattdessen einen Arbeitsvorrat mit Prioritäten anlegen. Holen Sie sich die Aufgaben aus der Warteschlange, die über einen Thread-Pool geplant werden. Sicherstellen, dass kein Thread jemals bei einer Warteoperation blockiert, sondern die Aufgabe in die Warteschlange zurückgestellt wird (mit einer niedrigeren Priorität) .

Was Sie also tun würden (z. B. beim Empfang einer OutOfMemory-Ausnahme), ist, denselben Auftrag/dieselbe Aufgabe in die Warteschlange zu stellen und die aktuelle Aufgabe zu beenden, wodurch der Thread für eine andere Aufgabe frei wird.

Ein einfacher Ansatz ist die Verwendung von LIFO, wodurch sichergestellt wird, dass eine Aufgabe, die in die Warteschlange gestellt wird, eine "niedrigere Priorität" hat als alle anderen Aufgaben, die sich bereits in dieser Warteschlange befinden.

1voto

sll Punkte 59134

Seit .NET Framework 4 haben wir eine API für die Arbeit mit der guten alten Memory-Mapped-Dateien Funktion, die seit vielen Jahren in der Win32API verfügbar ist, können Sie nun im .NET Managed Code verwenden.

Für Ihre Aufgabe ist die Option "Persisted memory-mapped files" besser geeignet, MSDN :

Persistierte Dateien sind speicherabgebildete Dateien, die mit einem Quelldatei auf einem Datenträger zugeordnet sind. Wenn der letzte Prozess die Arbeit mit der Datei beendet hat, werden die Daten in der Quelldatei auf der Festplatte gespeichert. Diese memory-mapped files eignen sich für die Arbeit mit extrem großen Quelldateien.

Auf der Seite von MemoryMappedFile.CreateFromFile() Methodenbeschreibung finden Sie ein schönes Beispiel, in dem beschrieben wird, wie man eine Memory-Mapped Views für die extrem große Datei erstellt.

EDIT: Update zu erheblichen Hinweisen in Kommentaren

Gerade gefundene Methode MemoryMappedFile.CreateViewStream() die einen Stream des Typs MemoryMappedViewStream die von einem der folgenden Elemente geerbt wird System.IO.Stream . Ich glaube, Sie können eine Instanz von XmlReader aus diesem Stream erstellen und dann Ihre benutzerdefinierte Implementierung der XslTransform mit diesem Leser/Strom instanziieren.

EDIT2: remi bourgarel (OP) bereits getestet diesen Ansatz und sieht aus wie diese besondere XslTransform Implementierung (ich frage mich, ob ANY würde) nicht mit MM-View-Stream in der Weise, die angenommen wurde arbeiten

0voto

Dalibor Čarapić Punkte 2528

Das Hauptproblem ist, dass Sie die gesamte Xml-Datei laden. Wenn Sie nur transformieren-wie-du-liest, sollte das Problem des fehlenden Speichers normalerweise nicht auftreten. Das heißt, ich fand einen MS-Support-Artikel, der vorschlägt, wie es getan werden kann: http://support.microsoft.com/kb/300934

Haftungsausschluss: Ich habe dies nicht getestet. Wenn Sie es verwenden und es funktioniert, lassen Sie es uns bitte wissen.

0voto

Tim Lloyd Punkte 37154

Sie könnten in Erwägung ziehen, eine Warteschlange zu verwenden, um zu drosseln, wie viele gleichzeitige Transformationen auf der Grundlage einer künstlichen Speichergrenze, z. B. der Dateigröße, durchgeführt werden. Etwas wie das Folgende könnte verwendet werden.

Diese Art von Drosselungsstrategie kann mit der maximalen Anzahl gleichzeitig verarbeiteter Dateien kombiniert werden, um sicherzustellen, dass Ihre Festplatte nicht zu sehr belastet wird.

NB Ich habe den notwendigen Versuch nicht einbezogen \catch\finally um die Ausführung herum, um sicherzustellen, dass Ausnahmen an den aufrufenden Thread weitergegeben und Waithandles immer freigegeben werden. Ich könnte hier noch weiter ins Detail gehen.

public static class QueuedXmlTransform
{
    private const int MaxBatchSizeMB = 300;
    private const double MB = (1024 * 1024);
    private static readonly object SyncObj = new object();
    private static readonly TaskQueue Tasks = new TaskQueue();
    private static readonly Action Join = () => { };
    private static double _CurrentBatchSizeMb;

    public static string Transform(string xsltPath, string xmlPath)
    {
        string tempPath = Path.GetTempFileName();

        using (AutoResetEvent transformedEvent = new AutoResetEvent(false))
        {
            Action transformTask = () =>
            {
                MvpXslTransform transform = new MvpXslTransform();

                transform.Load(xsltPath, new XsltSettings(true, false),
                    new XmlUrlResolver());

                using (StreamWriter writer = new StreamWriter(tempPath))
                using (XmlReader reader = XmlReader.Create(xmlPath))
                {
                    transform.Transform(new XmlInput(reader), null,
                        new XmlOutput(writer));
                }

                transformedEvent.Set();
            };

            double fileSizeMb = new FileInfo(xmlPath).Length / MB;

            lock (SyncObj)
            {
                if ((_CurrentBatchSizeMb += fileSizeMb) > MaxBatchSizeMB)
                {
                    _CurrentBatchSizeMb = fileSizeMb;

                    Tasks.Queue(isParallel: false, task: Join);
                }

                Tasks.Queue(isParallel: true, task: transformTask);
            }

            transformedEvent.WaitOne();
        }

        return tempPath;
    }

    private class TaskQueue
    {
        private readonly object _syncObj = new object();
        private readonly Queue<QTask> _tasks = new Queue<QTask>();
        private int _runningTaskCount;

        public void Queue(bool isParallel, Action task)
        {
            lock (_syncObj)
            {
                _tasks.Enqueue(new QTask { IsParallel = isParallel, Task = task });
            }

            ProcessTaskQueue();
        }

        private void ProcessTaskQueue()
        {
            lock (_syncObj)
            {
                if (_runningTaskCount != 0) return;

                while (_tasks.Count > 0 && _tasks.Peek().IsParallel)
                {
                    QTask parallelTask = _tasks.Dequeue();

                    QueueUserWorkItem(parallelTask);
                }

                if (_tasks.Count > 0 && _runningTaskCount == 0)
                {
                    QTask serialTask = _tasks.Dequeue();

                    QueueUserWorkItem(serialTask);
                }
            }
        }

        private void QueueUserWorkItem(QTask qTask)
        {
            Action completionTask = () =>
            {
                qTask.Task();

                OnTaskCompleted();
            };

            _runningTaskCount++;

            ThreadPool.QueueUserWorkItem(_ => completionTask());
        }

        private void OnTaskCompleted()
        {
            lock (_syncObj)
            {
                if (--_runningTaskCount == 0)
                {
                    ProcessTaskQueue();
                }
            }
        }

        private class QTask
        {
            public Action Task { get; set; }
            public bool IsParallel { get; set; }
        }
    }
}

Update

Fehler bei der Beibehaltung der Stapelgröße beim Wechsel zum nächsten Stapelfenster behoben:

_CurrentBatchSizeMb = fileSizeMb;

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X