Speicherleck bei der Verwendung von Threads
Ich erscheinen, um einen Speicherverlust in diesem Stück code. Es ist eine Konsolenanwendung, die schafft ein paar Klassen (WorkerThread), von denen jede meint, die Konsole in den angegebenen Intervallen. Das Threading.Timer wird verwendet, um dies zu tun, damit das schreiben auf die Konsole erfolgt in einem separaten thread (der TimerCallback aufgerufen wird, in einem separaten thread entnommen aus dem ThreadPool). Um Angelegenheiten zu erschweren, die Klasse MainThread Haken an der Veränderten Veranstaltung für den FileSystemWatcher; wenn die test.xml Datei ändert, wird der WorkerThread Klassen neu erstellt werden.
Jedes mal, wenn die Datei gespeichert ist, (jedes mal, wenn der WorkerThread und somit der Timer neu erstellt wird), der Speicher im Task-Manager erhöht (Mem Usage, und manchmal auch die VM-Größe); ferner in .Net Memory Profiler (v3.1), die Undisposed Instanzen der Klasse WorkerThread erhöht sich um zwei (das kann ein Roter Hering, aber da ich gelesen habe, dass .Net Memory Profiler hatte einen bug, wobei es kämpfte, um zu erkennen, entsorgt Klassen.
Anyway, hier ist der code - weiß jemand, was ist falsch?
BEARBEITEN: ich habe bewegt der Klasse die Schöpfung aus dem FileSystemWatcher.Geändert Ereignishandler, was bedeutet, dass der WorkerThread Klassen sind immer in den gleichen thread. Ich habe einen gewissen Schutz für die statischen Variablen. Ich habe auch zur Verfügung gestellt threading-Informationen zeigen deutlich, was Los ist, und haben vertauschen mithilfe der Timer mit einer expliziten Thread; aber die Erinnerung ist immer noch undicht! Die CPU-Auslastung erhöht sich langsam die ganze Zeit (ist das einfach nur wegen der extra text in der Konsole-Fenster?), und die VM-Größe erhöht, wenn ich die Datei ändern. Hier ist die neueste version des Codes:
BEARBEITEN Dies scheint in Erster Linie ein problem mit der Konsole über dem Speicher, wie Sie es schreiben. Es gibt noch ein problem mit der ausdrücklichen, SCHRIFTLICHEN Threads steigt die Speicherauslastung. Sehen unten meine Antwort.
class Program
{
private static List<WorkerThread> threads = new List<WorkerThread>();
static void Main(string[] args)
{
MainThread.Start();
}
}
public class MainThread
{
private static int _eventsRaised = 0;
private static int _eventsRespondedTo = 0;
private static bool _reload = false;
private static readonly object _reloadLock = new object();
//to do something once in handler, though
//this code would go in onStart in a windows service.
public static void Start()
{
WorkerThread thread1 = null;
WorkerThread thread2 = null;
Console.WriteLine("Start: thread " + Thread.CurrentThread.ManagedThreadId);
//watch config
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = "../../";
watcher.Filter = "test.xml";
watcher.EnableRaisingEvents = true;
//subscribe to changed event. note that this event can be raised a number of times for each save of the file.
watcher.Changed += (sender, args) => FileChanged(sender, args);
thread1 = new WorkerThread("foo", 10);
thread2 = new WorkerThread("bar", 15);
while (true)
{
if (_reload)
{
//create our two threads.
Console.WriteLine("Start - reload: thread " + Thread.CurrentThread.ManagedThreadId);
//wait, to enable other file changed events to pass
Console.WriteLine("Start - waiting: thread " + Thread.CurrentThread.ManagedThreadId);
thread1.Dispose();
thread2.Dispose();
Thread.Sleep(3000); //each thread lasts 0.5 seconds, so 3 seconds should be plenty to wait for the
//LoadData function to complete.
Monitor.Enter(_reloadLock);
thread1 = new WorkerThread("foo", 10);
thread2 = new WorkerThread("bar", 15);
_reload = false;
Monitor.Exit(_reloadLock);
}
}
}
//this event handler is called in a separate thread to Start()
static void FileChanged(object source, FileSystemEventArgs e)
{
Monitor.Enter(_reloadLock);
_eventsRaised += 1;
//if it was more than a second since the last event (ie, it's a new save), then wait for 3 seconds (to avoid
//multiple events for the same file save) before processing
if (!_reload)
{
Console.WriteLine("FileChanged: thread " + Thread.CurrentThread.ManagedThreadId);
_eventsRespondedTo += 1;
Console.WriteLine("FileChanged. Handled event {0} of {1}.", _eventsRespondedTo, _eventsRaised);
//tell main thread to restart threads
_reload = true;
}
Monitor.Exit(_reloadLock);
}
}
public class WorkerThread : IDisposable
{
private System.Threading.Timer timer; //the timer exists in its own separate thread pool thread.
private string _name = string.Empty;
private int _interval = 0; //thread wait interval in ms.
private Thread _thread = null;
private ThreadStart _job = null;
public WorkerThread(string name, int interval)
{
Console.WriteLine("WorkerThread: thread " + Thread.CurrentThread.ManagedThreadId);
_name = name;
_interval = interval * 1000;
_job = new ThreadStart(LoadData);
_thread = new Thread(_job);
_thread.Start();
//timer = new Timer(Tick, null, 1000, interval * 1000);
}
//this delegate instance does NOT run in the same thread as the thread that created the timer. It runs in its own
//thread, taken from the ThreadPool. Hence, no need to create a new thread for the LoadData method.
private void Tick(object state)
{
//LoadData();
}
//Loads the data. Called from separate thread. Lasts 0.5 seconds.
//
//private void LoadData(object state)
private void LoadData()
{
while (true)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(string.Format("Worker thread {0} ({2}): {1}", _name, i, Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(50);
}
Thread.Sleep(_interval);
}
}
public void Stop()
{
Console.WriteLine("Stop: thread " + Thread.CurrentThread.ManagedThreadId);
//timer.Dispose();
_thread.Abort();
}
#region IDisposable Members
public void Dispose()
{
Console.WriteLine("Dispose: thread " + Thread.CurrentThread.ManagedThreadId);
//timer.Dispose();
_thread.Abort();
}
#endregion
}
Gute Idee. Ersetzen Sie den timer mit einem expliziten thread, der einfach wartet, produziert genau das gleiche Ergebnis.
Aus einer Laune heraus habe ich kopiert den (neuen) code in ein neues Projekt und versuchte es - es scheint perfekt im Einklang mit standard-garbage-collection-Verhalten "Leck", wie es funktioniert. Zwingt eine GC.Collect() in deiner main-Schleife Stoppt die Leckagen insgesamt (Leistung, Kosten, natürlich). Bin ich etwas fehlt?
Interessant. Vermutlich waren Sie threads erstellen, anstatt den timer (durch das verwenden der timer ist meine bevorzugte Wahl)? Ich bin kein Experte auf dem Müll-Sammlung: nach welcher Zeit erwarten Sie in den Speicher übernommen durch die Anwendung zu stoppen erhöhen?
InformationsquelleAutor darasd | 2009-01-27
Du musst angemeldet sein, um einen Kommentar abzugeben.
Haben Sie zwei Probleme, die beiden zu trennen:
Im Watcher.Verändert die Prozedur-Aufruf-Thread.Sleep(3000);
Das ist schlechtes benehmen einen Rückruf von einem Faden, den Sie nicht besitzen (da es geliefert wird durch den pool im Besitz/verwendet, durch die Wächter. Dies ist nicht die Quelle des Problems aber. Diese in direkte Verletzung der Richtlinien für die Verwendung
Verwenden Sie die Statik und alle über dem Platz, die ist schrecklich, und hat wahrscheinlich führte Sie in dieses problem:
Dieser callback kann Feuer wiederholt auf mehreren verschiedenen threads (es verwendet die system-thread-pool für) der code wird davon ausgegangen, dass nur ein thread wird immer diese Methode ausführen in einer Zeit, da threads erstellt werden kann, aber nicht gestoppt.
Vorstellen: thread A und B
Haben Sie jetzt 4 WorkerThread Instanzen auf dem heap, aber nur zwei Variablen referenzieren, die zwei erzeugt, die von Einem zugespielt haben. Das event-handling und callback-Registrierung mit dem timer bedeutet, dass diese durchgesickert WorkerThreads am Leben gehalten werden (in der GC Sinne), trotz dass Sie keine Bezugnahme auf diese in Ihrem code. bleiben Sie ausgelaufen für immer.
Gibt es andere Fehler im design, aber dies ist eine kritische.
In der Tat - ich denke, Verschrottung, Nacharbeit es aus der Sicht von nicht erstellen, mehrere Timer, ist das, was benötigt wird. Das Verständnis der Semantik der FileSystemWatcher erste ist kritisch obwohl.
Ja, ich war das erreichen der gleichen Schlussfolgerung selbst. Ich wusste gar nicht, dass der FileSystemWatcher-Changed-Ereignis-handler wäre in einem neuen thread.
InformationsquelleAutor ShuggyCoUk
Nein, Nein, Nein, Nein, Nein, Nein, Nein. Verwenden Sie niemals Thread.Abort().
Lesen Sie die MSDN docs.
Der thread ist nicht garantiert, Abbrechen, sofort, oder überhaupt. Diese situation kann auftreten, wenn ein thread hat eine unbegrenzte Menge an Berechnung, die in der finally-Blöcke, die als Teil der die abort-Prozedur, dadurch auf unbestimmte Zeit verzögern Abbruch. Zu warten, bis ein thread abgebrochen hat, können Sie die Join-Methode auf den thread, der nach Aufruf der Abort-Methode, aber es gibt keine Garantie das warten zu Ende.
Den richtigen Weg, um einen thread zu signalisieren, dass es enden sollte, dann Aufruf von Join() auf diesen thread. Normalerweise mache ich so etwas (pseudo-code):
Ich dachte, dass es vorbei - ich werde sehen, ob ich kann kommen mit etwas, das Teil der Frage 🙂
Nein, Nein, Nein! Nicht verwenden Boolesche Werte. Verwenden ManualResetEvent zu signalisieren, einen thread zu stoppen.
Patrik - Boolesche Werte sind in Ordnung, solange Sie sperren um den Zugang zu Ihnen. ManualResetEvents sind komplizierter unnötig für solch einen einfachen Vorgang.
InformationsquelleAutor Rob
Gut, hatte einige Zeit, sich in diesen wieder, es scheint, dass der Speicherverlust ist ein bisschen ein Ablenkungsmanöver. , Wenn ich aufhöre zu schreiben, die Konsole, die Speicher-Nutzung Stoppt steigenden.
Es ist jedoch eine Verbleibende Problem, dass jedes mal ich editieren Sie die test.xml Datei (die feuert das Geänderte Ereignis auf dem FileSystemWatcher, dessen handler setzt flags, die Ursache der worker-Klassen erneuert werden und daher threads/Timer gestoppt werden), der Arbeitsspeicher erhöht sich von etwa 4K, vorausgesetzt, ich bin mit expliziten Threads, sondern der Timer. Wenn ich mit einem Timer, es ist kein problem. Aber angesichts der Tatsache, dass ich lieber mit einem Timer als Thread, ist dies nicht länger ein Problem für mich, aber ich würde immer noch interessieren, warum es passiert.
Sehen Sie den neuen code unten. Ich habe zwei-Klassen - WorkerThread und WorkerTimer, von denen man Threads verwendet, und der andere Timer (ich habe versucht, zwei Timer, das System.Threading.Timer und System.Timer.Timer. mit der Ausgabe der Konsole eingeschaltet ist, können Sie sehen, welchen Unterschied dies macht im Hinblick auf die thread-das tick-Ereignis ausgelöst wird). Nur Kommentar/kommentieren Sie die entsprechenden Zeilen im MainThread.Starten, um zu verwenden Sie die erforderliche Klasse. Aus dem oben genannten Grund, es wird empfohlen, dass die Konsole.WriteLine Zeilen auskommentiert sind, außer wenn Sie wollen, um zu überprüfen, dass alles wie erwartet funktioniert.
InformationsquelleAutor darasd
Gut Sie eigentlich nie nennen
dispose
auf der WorkerThread Instanzen.Sind Sie sicher, dass Ihr das gleiche von den profilern Sicht?
InformationsquelleAutor Greg Dean
Den tatsächlichen worker-threads werden nicht entsorgt, wenn die beobachtete Datei-Ereignis Auftritt. Ich denke, würde ich diese umschreiben, so dass neue threads werden nicht erstellt, aber Sie werden neu initialisiert. Anstelle von aufrufen
Stop
und Neuerstellen des threads, rufen Sie einen neuenRestart
Methode, dass Sie einfach Stoppt und setzt den timer.InformationsquelleAutor tvanfosson
Sie nie beenden des threads - so etwas wie den Process Explorer, um zu überprüfen, ob die Anzahl der Threads steigt als auch Speicher. Fügen Sie einen Aufruf von Abort() in der Stop () - Methode.
Edit: hast Du, danke.
InformationsquelleAutor lotsoffreetime