Freigeben über


Why not while(true)?

Many programs have a basic structure of:

 while (true)
{
    // Program goes here
}

This approach is logical if your program is responsible for monitoring user input, device I/O, threads, etc.  However, it's not an optimal approach for most Gadgeteer programs.  The rest of this post will explore the Gadgeteer event dispatcher, execution model, and best practices for utilizing their benefits.

How Gadgeteer Programs Run

When you write a Gadgeteer program, the code you want to run first should go into the ProgramStarted method.  The system calls this method after the hardware is booted and the event dispatcher is running.  ProgramStarted is a good place to create event handlers, set initial state in modules, and show a welcome screen if you're using a display.

However, you don't want to run an infinite loop in ProgramStarted.  Why not?  Because none of your event handlers will get called until after ProgramStarted exits.  That includes any timers you've created.

This happens because modules send events to a queue which is processed by the same thread that executes ProgramStarted. If ProgramStarted never exits, the dispatcher never starts.

The .NET Micro Framework Dispatcher

Gadgeteer uses an event dispatcher to funnel events from modules and timers to your program.  Many modules do processing on their own threads, but they send events to a dispatcher on the main program thread.  That dispatcher routes events to the appropriate event handlers in your code.

That means you don't need to worry about multi-threading (and its pitfalls like race conditions) unless you choose to create multiple threads yourself.  Any multithreading done by modules and timers is strictly behind the scenes.

The dispatcher makes it easy to work with devices that work asynchronously, too.  For example, many camera modules take a while (in microprocessor clock cycle terms) to capture a picture, encode it into a useful format and send the data to the main processor.  Networking events happen sporadically, and your application shouldn't spend time polling the network if it's not active.  Same with user input components - you want to know when the user presses or releases a button, but you don't want to spend processor resources on it otherwise.

The tricky thing is that the dispatcher runs on the same thread as your program code, so you have to give it time to run.  Any method which contains an infinite loop (or which takes a very long time to complete) will prevent the dispatcher from notifying your program about important events.

Care and Feeding of the Dispatcher

There are two design patterns which work very well for Gadgeteer programs.

1)  Register event handlers whenever possible. Don't block on user or device input, as this will prevent other parts of your project from functioning.

2)  Create a Gadgeteer.Timer and listen for its Tick event.  Any operation which you want to do at regular intervals is a good candidate for a timer. 

Here's an example of a program which controls a Button module that has an onboard LED.  The LED is set to blink on and off at a 1 second interval, and the program prints debug text when the user presses the button.

 public partial class Program
    {
        GT.Timer ledBlinkTimer = new GT.Timer(1000);
 
        void ProgramStarted()
        {
            button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);
            ledBlinkTimer.Tick += new GT.Timer.TickEventHandler(ledBlinkTimer_Tick);
             ledBlinkTimer.Start();
         }
 
        void ledBlinkTimer_Tick(GT.Timer timer)
        {
            if (button.IsLedOn == true)
            {
                button.TurnLEDOff();
            }
 
            else
                button.TurnLEDOn();
        }
 
        void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            Debug.Print("Button Pressed");   
        }
    }

This program will not block the dispatcher from running, and it won't spend a lot of processor cycles listening for button events.

The corresponding anti-pattern for Gadgeteer looks like this:

     public partial class Program
    {
        GT.Timer ledBlinkTimer = new GT.Timer(1000);
 
        // Don't do this with Gadgeteer.  It won't work!
        void ProgramStarted()
        {
            ledBlinkTimer.Tick += new GT.Timer.TickEventHandler(ledBlinkTimer_Tick);
            ledBlinkTimer.Start();
 
            // This while loop prevents the timer event from firing
            while (true)
            {
                if (button.IsPressed == true)
                {
                    Debug.Print("Button pressed");
                }
            }
            
        }
 
        // This event handler is never called because the program thread is blocked!
        void ledBlinkTimer_Tick(GT.Timer timer)
        {
            if (button.IsLedOn == true)
            {
                button.TurnLEDOff();
            }
 
            else
                button.TurnLEDOn();
        } 

This pattern is quite common, especially for embedded devices.  It's not wrong, but it doesn't work with the Gadgeteer dispatcher, which is essentially a while(true) loop itself!

Questions?

The Microsoft Research team who created Gadgeteer hangs out on the Gadgeteer forums.  We're also on Twitter (@netgadgeteer) and Facebook if you want to find us.

Comments

  • Anonymous
    December 20, 2011
    Great article!  This definitely solves some problems I've been having.  I wasn't aware of this difference between Gadgeteer & NETMF.  Thanks!

  • Anonymous
    December 20, 2011
    Thank for sharing, ver informative.. and i have a question for you. So how do initialize module out side of the Dispatcher? Basically how do i do this: Gadgeteer.Modules.GHIElectronics.Button button; button = new GTM.GHIElectronics.Button(14); button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed); outside of  ProgramStarted() or after that dispatch has Exited ... right now i get the following error: "modules must be created on the Program's Dispatcher thread" why i need to do this you ask? well because i need to be able to configure my Gadgeteer modules dynamically meaning i can load the program once and be able to plug and unplug modules later (NOT WHILE POWERED) and change the socket of the module and still be able to use it at runtime by recreate the class as mentioned above.

  • Anonymous
    December 20, 2011
    Got the answer i was looking for with the help of Architect http://www.breakcontinue.com a great contributor to the .net micro framework... here is the code i came up with ..: and works great ! void myMethod() {        Dispatcher.BeginInvoke(                new DispatcherOperationCallback(                    delegate(object o)                    {                        Debug.Print("Dispatch Called");                        Gadgeteer.Modules.GHIElectronics.Button button;                        button = new GTM.GHIElectronics.Button(14);                        button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);                        return null;                    }                    ), null                ); } Cheers.. Jay.

  • Anonymous
    December 20, 2011
    Hi Jay, Glad you found the answer you needed - you can run your own dispatcher on a different thread than the program thread, and it will send events to the handlers registered on that thread.  You'll still need some way to tell at startup which modules are plugged into which sockets though, correct?  I can't think of a way to do that with Gadgeteer as it is today, unless you are feeding that information to the system via a storage or network device. -- Kerry

  • Anonymous
    December 21, 2011
    Hi Kerry, The thing is i didn't want my handlers to be declared at the Program Thread i was kind of looking for a way to declare them at a different thread, that's when i ran into the error that all module must be included in the Programs thread, which prompted using the above work around, and it works great, now i can add and declare Modules at will. and any time from any thread... the other issue that i have no solution to for now is releasing the module from the socket it is plugged into.. so if i attach a button to socket A i can't release it from that socket and use that socket for another module....which is OK for now because you can plug and un-plug Modules while the board has power... maybe one day when Gadgeteer goes plug and play...