It turned out that the timer was inadvertently being used by a BackgroundWorker thread. Like most classes, winforms timers are not thread safe so any behavior guarantees are out the window as soon as you start accessing them from different threads without synchronization measures.
Worse, winforms timers interact with the main application thread directly so in this case it is not possible to put such synchronization measures in place. I like to call such classes thread-hostile. Another sure way to create thread-hostile code is to use global variables; we have our fair share of such problems in our legacy code base.
The following sample reproduces the timer problem by accessing a timer from a ThreadPool worker thread; the timer will only be fired once instead of indefinitely as you might expect:
public partial class Form1 : Form { private System.Windows.Forms.Timer fTimer; public Form1() { InitializeComponent(); fTimer = new System.Windows.Forms.Timer(); fTimer.Interval = 1000; fTimer.Tick += HandleTimerTick; fTimer.Start(); } private void HandleTimerTick(object sender, EventArgs args) { // sabotage timer by stopping/starting it from another thread ThreadPool.QueueUserWorkItem( delegate { fTimer.Stop(); fTimer.Start(); } ); MessageBox.Show("Timer tick"); } }
In our case, the worker thread touched the timer in a much more indirect way: the background task was using a service which leaked side effects into the rest of the system via events, resulting in inadvertent multi-threaded access all over the place.
Conclusion: if you are going to do multi-threading, make sure threads are well-isolated and only communicate with the rest of the system via well defined synchronization points.
Wishlist item: wouldn't it be nice if you had to explicitly mark methods before they could be used by multiple threads? The C# compiler could then generate optional checks that make your code fail fast when there is an accidental "threading leak". It wouldn't surprise me if the language will actually grow such a debugging feature in the future; multi-threaded .NET programming is on the rise yet still wildly dangerous.
No comments:
Post a Comment