Threads, Static Fields & Abstract Classes Problem
-
Hi Folks, I'm hoping someone can help me with an issue I've just come across. I’ve written the demo code below which shows that if I use a timer to schedule events, unless I use a static “sync” variable, a second instance of a class may begin running before the first has completed. Rather than relying on folk remembering and repeating this logic, I’m hoping to put it into an abstract base class, which then calls a method in the subclass to do the processing, safe in the knowledge that another instance of that same class won’t run until this one’s completed. However, if there’s another class deriving from this base class, I’d want instances of the new class to be unaffected by instances of the first subclass (i.e. effectively I want the sync field to be static in the subclass, but available to the base class).
class Program { static void Main(string\[\] args) { Base one = new SubClass1(); Base two = new SubClass2(); Timer timer1 = new Timer(); Timer timer2 = new Timer(); timer1.Interval = 3000; timer1.Elapsed += new ElapsedEventHandler(one.Run); timer1.Enabled = true; timer2.Interval = 5000; timer2.Elapsed += new ElapsedEventHandler(two.Run); timer2.Enabled = true;System.Threading.Thread.Sleep(20000); Console.ReadKey();//allows the program to stay alive whilst the threads continue } } abstract class Base { private volatile static bool sync = false; private static object syncroot = string.Empty; public void Run(object source, ElapsedEventArgs e) { if (sync) return; lock (syncroot) { if (sync) return; sync = true; } RunSubClassCode(); sync = false; } protected abstract void RunSubClassCode(); } class SubClass1: Base { protected override void RunSubClassCode() { Console.WriteLine("1a"); System.Threading.Thread.Sleep(2000); Console.WriteLine("1b"); } } class SubClass2 : Base { protected override void RunSubClassCode() { Console.WriteLine("2a"); System.Threading.Thread.Sleep(2000); Console.WriteLine("2b"); } }
Has anyone come across a similar requirement before, or can y
-
Hi Folks, I'm hoping someone can help me with an issue I've just come across. I’ve written the demo code below which shows that if I use a timer to schedule events, unless I use a static “sync” variable, a second instance of a class may begin running before the first has completed. Rather than relying on folk remembering and repeating this logic, I’m hoping to put it into an abstract base class, which then calls a method in the subclass to do the processing, safe in the knowledge that another instance of that same class won’t run until this one’s completed. However, if there’s another class deriving from this base class, I’d want instances of the new class to be unaffected by instances of the first subclass (i.e. effectively I want the sync field to be static in the subclass, but available to the base class).
class Program { static void Main(string\[\] args) { Base one = new SubClass1(); Base two = new SubClass2(); Timer timer1 = new Timer(); Timer timer2 = new Timer(); timer1.Interval = 3000; timer1.Elapsed += new ElapsedEventHandler(one.Run); timer1.Enabled = true; timer2.Interval = 5000; timer2.Elapsed += new ElapsedEventHandler(two.Run); timer2.Enabled = true;System.Threading.Thread.Sleep(20000); Console.ReadKey();//allows the program to stay alive whilst the threads continue } } abstract class Base { private volatile static bool sync = false; private static object syncroot = string.Empty; public void Run(object source, ElapsedEventArgs e) { if (sync) return; lock (syncroot) { if (sync) return; sync = true; } RunSubClassCode(); sync = false; } protected abstract void RunSubClassCode(); } class SubClass1: Base { protected override void RunSubClassCode() { Console.WriteLine("1a"); System.Threading.Thread.Sleep(2000); Console.WriteLine("1b"); } } class SubClass2 : Base { protected override void RunSubClassCode() { Console.WriteLine("2a"); System.Threading.Thread.Sleep(2000); Console.WriteLine("2b"); } }
Has anyone come across a similar requirement before, or can y
you are correct, timer handlers can overlap (unless you use a System.Windows.Forms.Timer which has special characteristics). There are several ways to deal with that, which one you choose may depend on circumstances. Here is a scheme a like a lot because it is simple: - ask the timer to tick only once, then call your handler; - in the handler, when all is done, again arm the timer for a single shot. The advantage is there is no risk of overlap, and what you are doing in fact is time the idle part of the period, so if the handler takes 3 seconds, and you set the timer to 7 seconds, then your action will run every 10 seconds; if for some reason it takes longer, then all later timer activities will also come later. The scheme can easily be implemented using the System.Threading.Timer and its Change() method. If you really want to time the period, i.e. the start point (and not the idle gap), then it is always trickier to get it right under all circumstances. However IMO the fixed gap is what most applications want. :)
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read formatted code with indentation, so please use PRE tags for code snippets.
I'm not participating in frackin' Q&A, so if you want my opinion, ask away in a real forum (or on my profile page).
-
you are correct, timer handlers can overlap (unless you use a System.Windows.Forms.Timer which has special characteristics). There are several ways to deal with that, which one you choose may depend on circumstances. Here is a scheme a like a lot because it is simple: - ask the timer to tick only once, then call your handler; - in the handler, when all is done, again arm the timer for a single shot. The advantage is there is no risk of overlap, and what you are doing in fact is time the idle part of the period, so if the handler takes 3 seconds, and you set the timer to 7 seconds, then your action will run every 10 seconds; if for some reason it takes longer, then all later timer activities will also come later. The scheme can easily be implemented using the System.Threading.Timer and its Change() method. If you really want to time the period, i.e. the start point (and not the idle gap), then it is always trickier to get it right under all circumstances. However IMO the fixed gap is what most applications want. :)
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read formatted code with indentation, so please use PRE tags for code snippets.
I'm not participating in frackin' Q&A, so if you want my opinion, ask away in a real forum (or on my profile page).
Hey Luc, that's a great idea; thanks again for your help. For anyone following this thread (no pun intended), below is an example of the demo code, updated to use this idea (hopefully this agrees with what Luc's described - it definitely works as hoped).
class Example4 { public static void Test() { Base4 one = new SubClass4\_1(); Base4 two = new SubClass4\_2(); one.Go(); two.Go(); System.Threading.Thread.Sleep(20000); //give the threads time to do stuff before terminating one.Stop(); two.Stop(); } } abstract class Base4 { DateTime lastRun = DateTime.UtcNow; Timer timer = new Timer(); public void Go() { timer.AutoReset = false; //run once (until manually enabled in Again()) timer.Elapsed += new ElapsedEventHandler(OnElapsedTime); timer.Interval = GetInterval(); timer.Enabled = true; } public void Stop() { timer.Close(); timer.Dispose(); //nb: code in thread runs to completion even after timer has been disposed of } private void OnElapsedTime(object source, ElapsedEventArgs e) { lastRun = DateTime.UtcNow; RunSubClassCode(); Again(); } private void Again() { //acount for delay caused by the app running int interval = GetIntervalAccountForProcessingTime(); try //stops the "loop" when the timer is disposed of, without exception { timer.Interval = interval; timer.Enabled = true;//kick things off again } catch (ObjectDisposedException) { } } private int GetIntervalAccountForProcessingTime() { int interval = GetInterval(); interval -= DateTime.UtcNow.Subtract(lastRun).Milliseconds; interval = Math.Max(interval, 1); //if we've gone past the period, kick it off straight away //while (interval < GetInterval()) interval += GetInterval(); //if we want to wait for the next interval/slot, replace the above line with this code return interval; } protected abstract int GetInterval(); protected abstract void RunSubClassCode(); } class SubClass4\_1 :
-
Hey Luc, that's a great idea; thanks again for your help. For anyone following this thread (no pun intended), below is an example of the demo code, updated to use this idea (hopefully this agrees with what Luc's described - it definitely works as hoped).
class Example4 { public static void Test() { Base4 one = new SubClass4\_1(); Base4 two = new SubClass4\_2(); one.Go(); two.Go(); System.Threading.Thread.Sleep(20000); //give the threads time to do stuff before terminating one.Stop(); two.Stop(); } } abstract class Base4 { DateTime lastRun = DateTime.UtcNow; Timer timer = new Timer(); public void Go() { timer.AutoReset = false; //run once (until manually enabled in Again()) timer.Elapsed += new ElapsedEventHandler(OnElapsedTime); timer.Interval = GetInterval(); timer.Enabled = true; } public void Stop() { timer.Close(); timer.Dispose(); //nb: code in thread runs to completion even after timer has been disposed of } private void OnElapsedTime(object source, ElapsedEventArgs e) { lastRun = DateTime.UtcNow; RunSubClassCode(); Again(); } private void Again() { //acount for delay caused by the app running int interval = GetIntervalAccountForProcessingTime(); try //stops the "loop" when the timer is disposed of, without exception { timer.Interval = interval; timer.Enabled = true;//kick things off again } catch (ObjectDisposedException) { } } private int GetIntervalAccountForProcessingTime() { int interval = GetInterval(); interval -= DateTime.UtcNow.Subtract(lastRun).Milliseconds; interval = Math.Max(interval, 1); //if we've gone past the period, kick it off straight away //while (interval < GetInterval()) interval += GetInterval(); //if we want to wait for the next interval/slot, replace the above line with this code return interval; } protected abstract int GetInterval(); protected abstract void RunSubClassCode(); } class SubClass4\_1 :
you're welcome. :)
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read formatted code with indentation, so please use PRE tags for code snippets.
I'm not participating in frackin' Q&A, so if you want my opinion, ask away in a real forum (or on my profile page).