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
}
Können Sie die Arbeit tun, ohne einen timer und sehen, ob es dann immer noch undicht? Es würde Ihnen helfen zu bestimmen, ob der timer ist das eigentliche problem.
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

Schreibe einen Kommentar