Welcome to MSDN Blogs Sign in | Join | Help

Windows Mobile Team Blog

The Official Windows Mobile Team Blog
News Flash: System.Threading is ... threaded

AKA: "Why you shouldn't trust a driver guy to teach you how to write code in C#"

A while ago I wrote this cute little text game in C# for my Smartphone.  I used it for a long time with no difficulty.  But, when I recently moved it to a new device, it would occasionally hang.  Of course, I could never get it to happen under the debugger, but this week I went searching through all the code in the hang path.  It didn't make any sense.  The code would have needed to be multithreaded to hang.  So I called Brian Cross and asked if System.Threading.Timer ran in a different thread than the rest of the process. 

Now Brian is a good friend, so he just said, "Let me check ... yes."  What he should have said was, "Duh!  It even says 'Threading' in the name!"  Much to my chagrin and embarrassment, I was writing multithreaded code without realizing it.

I'm sharing this embarrassment with all of you for two reasons.  First, don't make the mistake I did.  If you're looking for a managed equivalent to WM_TIMER messages, you want System.Windows.Forms.Timer, not System.Threading.Timer. 

But the bigger deal is that I wrote a blog entry a while back called Power to the Developers part 1 in which I provided a sample that showed how to stop doing CPU intensive stuff after a period of inactivity.  And, yes, I used the multithreaded timer.  So not only was I doing it wrong, I was teaching all of you how to do it wrong too.  <sighs> 

Anyway, I've updated the managed side of the Animate Sample to use the Forms timer instead of the Threading timer.  If you've ever downloaded that sample, please get the new version.  Sorry for the confusion.

Mike Calligaro

Posted: Friday, October 14, 2005 12:27 PM by windowsmobile
Filed under:

Comments

Greg Smith said:

I don't think this is entirely true. Well, I can't vouch for how equivalent or not the System.Windows.Forms.Timer is to the "unmanaged" function you mention. But I think the advice should NOT be to entirely avoid System.Threading.Thread.Timer in managed code.

But here are my thoughts, having worked through it on my own C# code and reading voraciously while working through it.

The issue I think is whether you have code that modifies the GUI within the timer code itself. And if you don't (or if you MAYBE do by having GUI code conditional), you may still want to use the System.Threading version of the Timer so that you do not slow your GUI down while doing the functions of the timer. If you want to use the System.Threading version of the Timer AND you have GUI code (anything that changes System.Windows.Forms, for example), you can use the Invoke function of the Control you happen to be using. This essentially makes the method run in the same thread of the GUI, thus avoiding the "hang".

For gaming where you have drawing code in the timer firing method, you are probably exactly correct. But for other kinds of code, any that don't deal with the GUI, I think it's safe to use the System.Threading Timer.

So here's an example where you might still use System.Threading (and please excuse my pseudo code):

MAIN_METHOD
LOOP BEGIN
DO SOME PROCESSING
TURN_TIMER_ON using SYSTEM.WINDOWS.FORMS Timer
CHANGE GUI MAIN
LOOP END


TIMER_FIRING_METHOD
DO SOME PROCESSING
CHANGE GUI SUB
DO SOME MORE PROCESSING
CHANGE GUI AGAIN SUB
END TIMER_FIRING_METHOD


In this case, you might choose to use the System.Windows.Forms Timer. Or you can rearrange the code:

MAIN_METHOD
LOOP BEGIN
DO SOME PROCESSING
TURN_TIMER_ON using SYSTEM.THREADING Timer
CHANGE GUI MAIN
LOOP END


TIMER_FIRING_METHOD
DO SOME PROCESSING
Control.Invoke(GUI_METHOD1) // CHANGE GUI SUB
DO SOME MORE PROCESSING
Control.Invoke(GUI_METHOD2) // CHANGE GUI AGAIN SUB
END TIMER_FIRING_METHOD

GUI_METHOD1
CHANGE GUI SUB
END

GUI_METHOD2
CHANGE GUI AGAIN SUB
END

Now, actually *doing* this is a little difficult because I don't think the Invoke can take arguments (maybe only 1) so you might have to figure out how to get parameters to the GUI_METHODs, and you'll have to make sure that your code is "Thread Safe" if your PROCESSING in the MAIN method operates on the same data as the PROCESSING in your TIMER_FIRING method. (Which, come to think of it, may still be a problem, if your TIMER interrupts your PROCESSING in the MAIN_METHOD, hmmm).

Anyway, as always, it's not cut and dry, unless you just want everything in your GUI thread and don't want any threading at all (which maybe was your whole point)!

Greg Smith
Author, FeederReader - Pocket PC *direct* RSS text, audio, video, podcasts
www.FeederReader.com - Download on the Road
# October 15, 2005 7:54 AM

Daniel Moth said:

The reason your app hung is cause you didn't use Control.Invoke:
http://www.danielmoth.com/Blog/2004/10/invoke-cf-and-full-fx.html
# October 15, 2005 12:21 PM

Max Stafford said:

Greg. No, you don't use System.Threading.Timer only when your timer doesn't modify UI. If you've got 2 System.Threading.Timers writing to the same shared variable somewhere, or even worse, access shared container or performing multi-step operations on the same object, sooner or later your code will break. It can even pass QA, but users will encounter it sooner or later.
The fact that it's .NET doesn't make multi thread programming braindead easy. You should still think about these things.
# October 16, 2005 1:25 AM

windowsmobile said:

Thanks for the replies everyone. It's a good discussion. I need to be more clear on what I was trying to say. You can certainly use System.Threading.Timer, I just want to make sure people realize that doing so makes your code run multithreaded. The problem with my code wasn't that I used the threaded timer. It was that I used the threaded timer and didn't do anything to protect data structures that were changed by both threads. Of course, I didn't do that because I didn't realize I was in a different thread. (-:

There are definitely reasons to write multithreaded code, but you've got to go in with eyes open. When I was using the threaded timer but thinking it was running in the process' main thread, my eyes were definitely shut.

The simple solution for my code was to switch to a timer that worked the way I thought it did (so my assumptions were valid). For simple little "do this with the UI after a few seconds" sorts of timings, the single threaded timer is a nice, easy way to go.

Mike Calligaro
# October 17, 2005 12:26 PM

Greg Smith said:

Max and Mike,

Agreed. I said in the text "for other kinds of code, any that don't deal with the GUI, I think it's safe to use the System.Threading Timer" but I didn't mean it is ONLY safe to use System.Threading when you DON'T modify the GUI inside the fired method. In fact, my pseudo code shows using Control.Invoke for sections within the timer-fired method for code that DOES modify the GUI. And then I hinted about the other real danger that you both mention (and I definately agree): code operating on the same object.

I think we're all on the same track, here. Great article for opening eyes, Mike.

I agree with you!
# October 18, 2005 6:47 PM

Gurdeep said:

I have a related issue here. What I would like to know is that how come the code was working fine on the old device and failed on a new device.

My issue is that a code that I want to be multithreaded and hence using the System.Threading.Timer is working fine on Windows Mobile 5 devices but not on Windows Mobile 2003 or SE devices.
My thought is that maybe the old devices don't support multithreading and hence run the System.Threading.timer on the same thread.

Any thoughts?
Thanks.
# July 6, 2006 5:25 AM

MikeCal said:

Gurdeep, it's my understanding that System.Threading.Timer has always been multithreaded.  Certainly WM2003 itself supports multiple threads.  The very first version of Windows CE written back in the 90's supported threads.  And the standard version of .NET CF didn't change from WM2003 to WM5 (NET 2.0 shipped after WM5 did).  

So I'm not sure why your code is working in one OS and not the other.

Mike
# July 6, 2006 5:51 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Page view tracker