使用线程时内存泄漏

dar*_*asd 2 .net c# memory multithreading timer

我似乎在这段代码中有内存泄漏.它是一个控制台应用程序,它创建了几个类(WorkerThread),每个类都以指定的时间间隔写入控制台.所述Threading.Timer用于做到这一点,因此写入到控制台在一个单独的线程执行(该TimerCallback在从线程池截取的单独的线程调用).更复杂的是,MainThread类挂接到FileSystemWatcher的Changed事件; 当test.xml文件更改时,将重新创建WorkerThread类.

每一次,该文件被保存(每次所述的WorkerThread,因此定时器重新创建),在任务管理器增加存储器(内存使用,有时也VM大小); 此外,在.net内存分析器(V3.1),通过两个的WorkerThread类增加未予处置实例(这可能是一个红色的鲱鱼,但因为我读过的.Net内存分析器有一个bug由此挣扎检测处理班级.

无论如何,这是代码 - 有谁知道什么是错的?

编辑:我已经将类创建移出FileSystemWatcher.Changed事件处理程序,这意味着WorkerThread类总是在同一个线程中创建.我为静态变量添加了一些保护.我还提供了线程信息,以更清楚地显示正在发生的事情,并使用Timer与显式线程交换; 但是,内存仍然在泄漏!内存使用量一直在缓慢增加(这只是由于控制台窗口中的额外文本?),并且当我更改文件时VM大小增加.这是代码的最新版本:

编辑当你写入它时,这似乎主要是控制台使用内存的问题.显式编写的Threads仍然存在增加内存使用量的问题.请参阅下面的答案.

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
}
Run Code Online (Sandbox Code Playgroud)

Shu*_*oUk 8

你有两个问题,都是分开的:

在Watcher.Changed的处理程序中,您调用Thread.Sleep(3000); 这是你不拥有的线程回调中的不良行为(因为它是由观察者拥有/使用的池提供的.虽然这不是你问题的根源.这直接违反了使用指南

你在所有可怕的地方使用静力学,并且可能导致你陷入这个问题:

static void test()
{
    _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 (DateTime.Now.Ticks - _lastEventTicks > 1000)
    {
        Thread.Sleep(3000);
        _lastEventTicks = DateTime.Now.Ticks;
        _eventsRespondedTo += 1;
        Console.WriteLine("File changed. Handled event {0} of {1}.", _eventsRespondedTo, _eventsRaised);
        //stop threads and then restart them
        thread1.Stop();
        thread2.Stop();
        thread1 = new WorkerThread("foo", 20);
        thread2 = new WorkerThread("bar", 30);
    }
}
Run Code Online (Sandbox Code Playgroud)

此回调可以在多个不同的线程上重复触发(它使用系统线程池).您的代码假定一次只有一个线程将执行此方法,因为可以创建线程但不能停止线程.

想象一下:线程A和B.

  1. 一个thread1.Stop()
  2. 一个thread2.Stop()
  3. B thread1.Stop()
  4. B thread2.Stop()
  5. 一个thread1 =新的WorkerThread()
  6. 一个thread2 =新的WorkerThread()
  7. B thread1 = new WorkerThread()
  8. B thread2 = new WorkerThread()

您现在在堆上有4个WorkerThread实例,但只有两个引用它们的变量,由A创建的两个已泄露.使用计时器的事件处理和回调注册意味着泄漏的WorkerThreads保持活动(在GC意义上),尽管您的代码中没有引用它们.他们永远被泄露.

设计中还有其他缺陷,但这是一个至关重要的缺陷.