Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Well, having had some time to look into this again, it appears that the memory leak is a bit of a red herring. <em>When I stop writing to the console, the memory usage stops increasing</em>.</p> <p>However, there is a remaining issue in that every time I edit the test.xml file (which fires the Changed event on the FileSystemWatcher, whose handler sets flags that cause the worker classes to be renewed and therefore threads/timers to be stopped), the memory increases by about 4K, providing that I am using explicit Threads, rather Timers. When I use a Timer, there is no problem. But, given that I would rather use a Timer than a Thread, this is no longer an issue to me, but I would still be interested in why it is occuring.</p> <p>See the new code below. I've created two classes - WorkerThread and WorkerTimer, one of which uses Threads and the other Timers (I've tried two Timers, the System.Threading.Timer and the System.Timers.Timer. with the Console output switched on, you can see the difference that this makes with regards to which thread the tick event is raised on). Just comment/uncomment the appropriate lines of MainThread.Start in order to use the required class. For the reason above, it is recommended that the Console.WriteLine lines are commented out, except when you want to check that everything is working as expected.</p> <pre><code>class Program { 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; //WorkerTimer thread1 = null; //WorkerTimer 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) =&gt; FileChanged(sender, args); thread1 = new WorkerThread("foo", 10); thread2 = new WorkerThread("bar", 15); //thread1 = new WorkerTimer("foo", 10); //thread2 = new WorkerTimer("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); //GC.Collect(); thread1 = new WorkerThread("foo", 5); thread2 = new WorkerThread("bar", 7); //thread1 = new WorkerTimer("foo", 5); //thread2 = new WorkerTimer("bar", 7); _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 WorkerTimer : IDisposable { private System.Threading.Timer _timer; //the timer exists in its own separate thread pool thread. //private System.Timers.Timer _timer; private string _name = string.Empty; /// &lt;summary&gt; /// Initializes a new instance of the &lt;see cref="WorkerThread"/&gt; class. /// &lt;/summary&gt; /// &lt;param name="name"&gt;The name.&lt;/param&gt; /// &lt;param name="interval"&gt;The interval, in seconds.&lt;/param&gt; public WorkerTimer(string name, int interval) { _name = name; //Console.WriteLine("WorkerThread constructor: Called from thread " + Thread.CurrentThread.ManagedThreadId); //_timer = new System.Timers.Timer(interval * 1000); //_timer.Elapsed += (sender, args) =&gt; LoadData(); //_timer.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() { for (int i = 0; i &lt; 10; i++) { //Console.WriteLine(string.Format("Worker thread {0} ({2}): {1}", _name, i, Thread.CurrentThread.ManagedThreadId)); Thread.Sleep(50); } } public void Stop() { //Console.WriteLine("Stop: called from thread " + Thread.CurrentThread.ManagedThreadId); //_timer.Stop(); _timer.Change(Timeout.Infinite, Timeout.Infinite); //_timer = null; //_timer.Dispose(); } #region IDisposable Members public void Dispose() { //Console.WriteLine("Dispose: called from thread " + Thread.CurrentThread.ManagedThreadId); //_timer.Stop(); _timer.Change(Timeout.Infinite, Timeout.Infinite); //_timer = null; //_timer.Dispose(); } #endregion } public class WorkerThread : IDisposable { private string _name = string.Empty; private int _interval = 0; //thread wait interval in ms. private Thread _thread = null; private ThreadStart _job = null; private object _syncObject = new object(); private bool _killThread = false; public WorkerThread(string name, int interval) { _name = name; _interval = interval * 1000; _job = new ThreadStart(LoadData); _thread = new Thread(_job); //Console.WriteLine("WorkerThread constructor: thread " + _thread.ManagedThreadId + " created. Called from thread " + Thread.CurrentThread.ManagedThreadId); _thread.Start(); } //Loads the data. Called from separate thread. Lasts 0.5 seconds. // //private void LoadData(object state) private void LoadData() { while (true) { //check to see if thread it to be stopped. bool isKilled = false; lock (_syncObject) { isKilled = _killThread; } if (isKilled) return; for (int i = 0; i &lt; 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.ManagedThreadId + " called from thread " + Thread.CurrentThread.ManagedThreadId); //_thread.Abort(); lock (_syncObject) { _killThread = true; } _thread.Join(); } #region IDisposable Members public void Dispose() { //Console.WriteLine("Dispose: thread " + _thread.ManagedThreadId + " called from thread " + Thread.CurrentThread.ManagedThreadId); //_thread.Abort(); lock (_syncObject) { _killThread = true; } _thread.Join(); } #endregion } </code></pre>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload