Active Object


One of my favorite uses of the COMMAND pattern is the ACTIVE OBJECT pattern.[1] This old technique for implementing multiple threads of control has been used, in one form or another, to provide a simple multitasking nucleus for thousands of industrial systems.

[1] [Lavender96]

The idea is very simple. Consider Listings 21-2 and 21-3. An ActiveObjectEngine object maintains a linked list of Command objects. Users can add new commands to the engine, or they can call Run(). The Run() function simply goes through the linked list, executing and removing each command.

Listing 21-2. ActiveObjectEngine.cs

using System.Collections; public class ActiveObjectEngine {   ArrayList itsCommands = new ArrayList();   public void AddCommand(Command c)   {     itsCommands.Add(c);   }   public void Run()   {     while (itsCommands.Count > 0)     {       Command c = (Command) itsCommands[0];       itsCommands.RemoveAt(0);       c.Execute();     }   } }

Listing 21-3. Command.cs

public interface Command {   void Execute(); }

This may not seem very impressive. But imagine what would happen if one of the Command objects in the linked list put itself back on the list. The list would never go empty, and the Run() function would never return.

Consider the test case in Listing 21-4. This test case creates a SleepCommand, which among other things passes a delay of 1,000 ms to the constructor of the SleepCommand. The test case then puts the SleepCommand into the ActiveObjectEngine. After calling Run(), the test case expects that a certain number of milliseconds have elapsed.

Listing 21-4. TestSleepCommand.cs

using System; using NUnit.Framework; [TestFixture] public class TestSleepCommand {   private class WakeUpCommand : Command   {     public bool executed = false;     public void Execute()     {       executed = true;     }   }   [Test]   public void TestSleep()   {     WakeUpCommand wakeup = new WakeUpCommand();     ActiveObjectEngine e = new ActiveObjectEngine();     SleepCommand c = new SleepCommand(1000, e, wakeup);     e.AddCommand(c);     DateTime start = DateTime.Now;     e.Run();     DateTime stop = DateTime.Now;     double sleepTime = (stop-start).TotalMilliseconds;     Assert.IsTrue(sleepTime >= 1000,       "SleepTime " + sleepTime + " expected > 1000");     Assert.IsTrue(sleepTime <= 1100,       "SleepTime " + sleepTime + " expected < 1100");     Assert.IsTrue(wakeup.executed, "Command Executed");   } }

Let's look at this test case more closely. The constructor of the SleepCommand contains three arguments. The first is the delay time, in milliseconds. The second is the ActiveObjectEngine that the command will be running in. Finally, there is another command object called wakeup. The intent is that the SleepCommand will wait for the specified number of milliseconds and will then execute the wakeup command.

Listing 21-5 shows the implementation of SleepCommand. On execution, SleepCommand checks whether it has been executed previously. If not, it records the start time. If the delay time has not passed, it puts itself back in the ActiveObjectEngine. If the delay time has passed, it puts the wakeup command into the ActiveObjectEngine.

Listing 21-5. SleepCommand.cs

using System; public class SleepCommand : Command {   private Command wakeupCommand = null;   private ActiveObjectEngine engine = null;   private long sleepTime = 0;   private DateTime startTime;   private bool started = false;   public SleepCommand(long milliseconds, ActiveObjectEngine e,                       Command wakeupCommand)   {     sleepTime = milliseconds;     engine = e;     this.wakeupCommand = wakeupCommand;   }   public void Execute()   {     DateTime currentTime = DateTime.Now;     if (!started)     {       started = true;       startTime = currentTime;       engine.AddCommand(this);     }     else     {       TimeSpan elapsedTime = currentTime - startTime;       if (elapsedTime.TotalMilliseconds < sleepTime)       {         engine.AddCommand(this);       }       else       {         engine.AddCommand(wakeupCommand);       }     }   } }

We can draw an analogy between this program and a multithreaded program that is waiting for an event. When a thread in a multithreaded program waits for an event, the thread usually invokes an operating system call that blocks the thread until the event has occurred. The program in Listing 21-5 does not block. Instead, if the event it is waiting for (elapsedTime.TotalMilliseconds < sleepTime) has not occurred, the thread simply puts itself back into the ActiveObjectEngine.

Building multithreaded systems using variations of this technique has been, and will continue to be, a very common practice. Threads of this kind have been known as run-to-completion tasks (RTC); each Command instance runs to completion before the next Command instance can run. The name RTC implies that the Command instances do not block.

The fact that the Command instances all run to completion gives RTC threads the interesting advantage that they all share the same runtime stack. Unlike the threads in a traditional multithreaded system, it is not necessary to define or allocate a separate runtime stack for each RTC thread. This can be a powerful advantage in memory-constrained systems with many threads.

Continuing our example, Listing 21-6 shows a simple program that makes use of SleepCommand and exhibits multithreaded behavior. This program is called DelayedTyper.

Listing 21-6. DelayedTyper.cs

using System; public class DelayedTyper : Command {   private long itsDelay;   private char itsChar;   private static bool stop = false;   private static ActiveObjectEngine engine =     new ActiveObjectEngine();   private class StopCommand : Command   {     public void Execute()     {       DelayedTyper.stop = true;     }   }   public static void Main(string[] args)   {     engine.AddCommand(new DelayedTyper(100, '1'));     engine.AddCommand(new DelayedTyper(300, '3'));     engine.AddCommand(new DelayedTyper(500, '5'));     engine.AddCommand(new DelayedTyper(700, '7'));     Command stopCommand = new StopCommand();     engine.AddCommand(       new SleepCommand(20000, engine, stopCommand));     engine.Run();   }   public DelayedTyper(long delay, char c)   {     itsDelay = delay;     itsChar = c;   }   public void Execute()   {     Console.Write(itsChar);     if (!stop)       DelayAndRepeat();   }   private void DelayAndRepeat()   {     engine.AddCommand(       new SleepCommand(itsDelay, engine, this));   } }

Note that DelayedTyper implements Command. The Execute method simply prints a character that was passed at construction, checks the stop flag and, if not set, invokes DelayAndRepeat. The DelayAndRepeat constructs a SleepCommand, using the delay that was passed in at construction, and then inserts the SleepCommand into the ActiveObjectEngine.

The behavior of this Command is easy to predict. In effect, it hangs in a loop, repeatedly typing a specified character and waiting for a specified delay. It exits the loop when the stop flag is set.

The Main program of DelayedTyper starts several DelayedTyper instances going in the ActiveObjectEngine, each with its own character and delay, and then invokes a SleepCommand that will set the stop flag after a while. Running this program produces a simple string of 1s, 3s, 5s, and 7s. Running it again produces a similar but different string. Here are two typical runs:

135711311511371113151131715131113151731111351113711531111357... 135711131513171131511311713511131151731113151131711351113117...


These strings are different because the CPU clock and the real-time clock aren't in perfect sync. This kind of nondeterministic behavior is the hallmark of multithreaded systems.

Nondeterministic behavior is also the source of much woe, anguish, and pain. As anyone who's worked on embedded real-time systems knows, it's tough to debug nondeterministic behavior.




Agile Principles, Patterns, and Practices in C#
Agile Principles, Patterns, and Practices in C#
ISBN: 0131857258
EAN: 2147483647
Year: 2006
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net