Real-time applications

Real-time computing happens when a system is able to guarantee its latency time, regardless of the system load. Obviously, low latency times are mandatory, but it is the ability to guarantee the same latency time as load increases that makes a fast system actually a real-time system. A more detailed definition is available here: http://en.wikipedia.org/wiki/Real-time_computing.

A canonical example is the ABS (anti-lock braking system) logic ubiquitously implemented in any automobile. Such logic must give results within a specific deadline (in a span of milliseconds), otherwise the system will go into a failed state.

Sometimes real-time systems may run at an acceptable service level, although with soft constraint specifications such as adding some tolerance to the deadline requirement. With such a near real-time requirement, we can code in .NET for Microsoft Windows as easily as we usually do for any other application type.

Bear in mind that within Microsoft Windows we cannot have full real-time computation, mainly because of the unavailability of any application to claim the 100 percent time of a CPU that is handled in time-share by the OS itself.

This does not mean that Windows or the CLR cannot run code fast enough for real-time, as real-time programming does not mean fast – it means with deterministic times. Windows cannot run real-time applications, simply because it cannot guarantee specific timing for (system – Win32) method invocations or thread start-up/stop times. The same happens regarding the CLR that cannot guarantee fixed timings about method execution and object destruction, for instance as has already been described in the Garbage Collection section in Chapter 3, CLR Internals.

When dealing with specific applications such as industrials systems for automations or robotics, it may be that we need near-real-time execution in C# to drive such automations. Although we cannot create a Computer Numerical Control (CNC) with C#, we can drive a CNC with remote invocations made in any .NET language. When driving a CNC, the best performing architecture in C# is made using task-parallelism or multi-thread based design.

If we have to create an application that reads a joystick position value, and then moves a robotic arm to a specific position, we should make at least a three-threaded application that can run at 60 FPS (Frame Per Second). This means that all C# code must execute in less than 16ms per cycle.

Real-time applications

A real-time queued driver processor made with C#

The application consists of a task, or a thread in charge of asking the position of the joystick with an infinite-loop in a polling design. No logic can be placed here because if any kind of logic is placed here, it would reduce the read speed, which, in turn, would reduce the overall FPS rates of the application.

Successively, any value will be queued in a valid thread-safe in-memory queue that will propogate the value without having to couple the two tasks processing speeds.

The second task will eventually check for data integrity and avoid data duplication by knowing the actual CNC state and thus avoid sending the same position to the next step multiple times.

The last task will read messages from the previous step by reading another queue and will later send any queued message to the CNC in the right sequence, because the whole queued architecture guarantees the sequential transmission of all messages.

For instance, you can run the real time application and later run a telnet client by executing such command: [telnet localhost 8080]. When the telnet will establish the connection to the real time application we can simply test it by writing some text in the telnet client one. All the text will be sent to the real time application and is later shown in the console.

static void Main(string[] args)
{
    //create all needed tasks and wait until any will exit
    //any task exit will be considered an error and will cause process exit
    Task.WaitAny(
        Task.Factory.StartNew(OnDataReaderTask, TaskCreationOptions.LongRunning),
        Task.Factory.StartNew(OnDataProcessorTask, TaskCreationOptions.LongRunning),
        Task.Factory.StartNew(OnDataWriterTask, TaskCreationOptions.LongRunning));

    Console.WriteLine("Abnormal exit!");
}

//a stopwatch for testing purposes
static readonly Stopwatch stopwatch = new Stopwatch();

//this task will read data from the reader source
//we will use a simple tcp listener for testing purposes
private static void OnDataReaderTask()
{
    //a listener for opening a server TCP port
    var listener = new TcpListener(IPAddress.Any, 8080);
    listener.Start();

    //the server client for communication with remote client
    using (var client = listener.AcceptTcpClient())
    using (var stream = client.GetStream())
        while (true)
        {
            //try reading next byte
            var nextByte = stream.ReadByte();

            //valid char
            if (nextByte >= 0)
            {
                AllMessagesQueue.Enqueue((char)nextByte);

                //start stopwatch
                stopwatch.Reset();
                stopwatch.Start();
            }

            Thread.Sleep(1);
        }
}

//this queue will contains temporary messages going from reader task to processor task
static readonly ConcurrentQueue<char> AllMessagesQueue = new ConcurrentQueue<char>();

//this task will process data messages
//no data repetition will be admitted
private static void OnDataProcessorTask()
{
    char last = default(char);
    while (true)
    {
        char c;
        //if there is some data to read
        if (AllMessagesQueue.TryDequeue(out c))
            //only new values are admitted when sending coordinates to a CNC
            if (c != last)
            {
                last = c;
                ValidMessagesQueue.Enqueue(c);
            }
            else
                //stop stopwatch
                stopwatch.Stop();

        Thread.Sleep(1);
    }
}

//this queue will contains temporary messages going from processor task to writer task
static readonly ConcurrentQueue<char> ValidMessagesQueue = new ConcurrentQueue<char>();

//this task will push data to the target system
//instead of a CNC we will use the Console for testing purposes
private static void OnDataWriterTask()
{
    while (true)
    {
        char c;
        //if there is some data to read
        if (ValidMessagesQueue.TryDequeue(out c))
        {
            //stop stopwatch
            stopwatch.Stop();
            Debug.WriteLine(string.Format("Message crossed tasks in {0:N0}ms", stopwatch.ElapsedMilliseconds));

            //we will send such data to the CNC system
            //for testing purposes we will use a Console.Write
            Console.Write(c);
        }

        Thread.Sleep(1);
    }
}

The preceding example shows how to process data without ever making any tasks wait for another. This solution will guarantee the message flow and executes in not more than 10ms on my laptop, so its speed is actually higher than 60 FPS, as required.

The usage of Thread.Sleep at 1ms will force CLR to pause the execution of the thread. On Windows, this stop-and-resume time is variable.

Obviously, we cannot guarantee that under load, the system will process in the same time, so this is definitely a near-real-time application with a soft constraint on deadlines specification; although optimistically, for 99.99% of the time, it works just fine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.51.153