我在這段代碼中似乎有內存泄漏。它是一個控制檯應用程序,它創建了幾個類(WorkerThread),每個類都以指定的時間間隔寫入控制檯。 Threading.Timer用於執行此操作,因此寫入控制檯是在單獨的線程中執行的(TimerCallback在從ThreadPool獲取的單獨線程中調用)。複雜的是,MainThread類掛接到FileSystemWatcher的Changed事件;當test.xml文件更改時,將重新創建WorkerThread類。每次保存文件時(每次重新創建WorkerThread並因此重新創建Timer),任務管理器中的內存將增加(內存使用率,有時也包括虛擬機大小)。此外,在.Net內存分析器(v3.1)中,WorkerThread類的未配置實例增加了兩個(但這可能是一個紅鯡魚,因爲我已經讀過.Net內存分析器有一個錯誤,它試圖檢測。處置類使用線程時內存泄漏
總之,這裏的代碼 - 沒有人知道什麼是錯
編輯:我搬到類創作出FileSystemWatcher.Changed事件處理程序,這意味着的WorkerThread類始終?我已經爲靜態變量添加了一些保護,我還提供了線程信息以更清楚地顯示發生了什麼,並且使用Timer使用顯式線程交換;但是,t他的記憶仍在泄漏!內存使用量一直在緩慢增加(這是否僅僅是由於控制檯窗口中的額外文本?),並且當我更改文件時,VM大小會增加。以下是最新版本的代碼:
編輯這似乎主要是控制檯在寫入內存時使用內存的問題。明確寫入的線程仍然存在增加內存使用率的問題。見my answer below。
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
}
你可以做沒有計時器的工作,看看它是否仍然顯得漏?它可以幫助您確定計時器是否是實際問題。 – StingyJack 2009-01-27 13:05:13
好主意。用一個明確的線程替換定時器,該線程只需等待產生完全相同的結果。 – darasd 2009-01-27 14:51:37
一時興起,我將(新)代碼複製到一個新項目中並嘗試使用它 - 它完全符合標準垃圾回收行爲以「泄漏」它的方式。在主循環中強制GC.Collect()完全停止泄漏(顯然,性能成本)。我錯過了什麼嗎? – lotsoffreetime 2009-01-29 15:43:25