Reputation: 1851
I had to implement two more asynch threads to my main ui thread.
One ( T_A1 ) fetches some db data and returns, ui may still be active and recieve user interactions ( perhaps to change in the future back to one thread) and another thread ( T_A2 )which polls some hardware for a special value. I also have a timer on my winforms-ui, a simple windows.forms.Timer object.
When T_A2 ( which is started in an own object ) throws an event to the ui thread to handle it, it is still in the asynch thread, but handled inside mainform. In this eventhandler I start the timer. ( Simply via timer.Start();)
I logged the tick event, but I do not get it, it simply never occurrs, so I have no log entry.
BUT when I start the timer with the help of Invoke/BeginInvoke I marshall the starting of the timer to be executed in the ui. AND then the tick event occurrs, and I get my log entry.
My question is : WHY?
If I do not use Invoke/BeginInvoke I start the timer in the thread, which throwed the event ( T_A2 ), so the tick would also be expected to occur in this thread, but the thread already died ?
Or what could be the reason ?
Upvotes: 0
Views: 588
Reputation: 63732
The simple answer is this:
System.Windows.Forms.Timer is tightly coupled to the message loop of your application. It depends completely on the message loop, and as such, it only works on the main UI thread. It does not create its own thread. There's no message loop on your T_A2 thread, so it just dies - and even if your thread continued, it would not cause any timer events.
If you want a timer that's not related to the message loop, you have to use System.Timers.Timer, which works in a completely different way, and will also work in your scenario.
Upvotes: 1
Reputation: 941515
The why? is simple, threading is dangerous. It has a knack for not doing what you hope it will do.
Many GUI classes in Winforms are not thread-safe, you must use Control.BeginInvoke to ensure that you modify their properties or call their methods on the exact same thread that created the objects. It is a pretty fundamental limitation, creating thread-safe code is difficult.
Even simple classes in .NET are not thread-safe, none of the collection classes are. You have some latitude with, say, List<>, you can make it thread-safe yourself by carefully using the lock keyword to ensure that the List object is only ever accessed from a single thread. Still pretty hard to do correctly.
That stops being practical when the class isn't simple and has non-trivial interactions with other classes and the operating system. Few classes in .NET are has heavy as Control, it has hundreds of methods and properties. So Microsoft itself just declared the job of making it thread-safe impossible, like they did with List<>, and there's no hope that you can add enough locking yourself to make it thread-safe. Deadlock is practically guaranteed. Nor did Microsoft give you enough access to the internals to inject the required locking, they didn't think it was worth it.
So you must only ever use the objects from the same thread to keep it safe, they did give you the required tools to make that easy. Which includes BeginInvoke and all kinds of extra help like BackgroundWorker, TaskScheduler.FromCurrentSynchronizationContext and the async/await keywords. And the InvalidOperationException you get when you do it wrong, a very helpful exception.
The specific implementation detail of the Timer class you ran into is that it is a pretty simple class and actually is thread-safe. Starting or stopping it on a worker thread works just fine and is a safe thing to do. Note that you didn't get the InvalidOperationException that you'd normally get when you violate threading requirements. Unfortunately, it depends on an implementation detail that you didn't take care of, and never would. Its Tick event is raised by a dispatcher loop, the kind you get from Application.Run(). And you didn't call Application.Run() on your worker thread. So the timer never ticks.
The proper solution here is to not call Application.Run() on your worker, that's one solution that gives you two new problems. You already have a perfectly good thread that called Application.Run(). Use Control.BeginInvoke() to use it.
Upvotes: 2