Following along with the translation of
the C# message framework to Ada of the previous post, I am reporting my
progress.
In C# the interface to Windows was available in their
(Microsoft's) system classes. In Ada,
via the GNAT libraries, it is necessary to use C interfaces. This post will describe those used to
implement my C# Threads class with its associated PeriodicTimer class - both of
which are in the Threads.cs file. The
PeriodicTimer class uses wait handles which is also used by the Disburse
class. Both PeriodicTimer and Disburse
use wait handles to signal the end of a wait to periodic components or those
components that are to be run when a message is added to their receive queue.
In this post, the Ada version of the Threads class and the
PeriodicTimer class will be presented along with wait events that allow a
periodic component to wait until its period interval has elapsed in order to
resume the component. The components
will only be test components to allow the wait events to be tested. The actual component to implement the
greatly reduced OFP will be done later after the necessary Ada packages have
been translated from their C# classes to allow for the delivery of messages
from the remote C# application.
Threads
In the C# application, the Threads Create method is run last
to allow the other classes to be instantiated and the user declared components
to install themselves into the application.
This allows separate threads to be created for each component that has
been registered with the Component class.
Then, for periodic components, an instance of the PeriodicTimer class is
instantiated so that it can signal the component when it is to resume.
The Ada package that implements this feature has been coded
in approximately the reverse order from the C# class. This is because Ada has to have the procedure or function
declared before it is referenced. Ada
allow a declaration of the routine prior to the code that implements it (called
the body) but I chose to just implement the routine without a separate
declaration so the implementation has to be placed after code that it
references.
And, of course, Ada doesn't know about Windows thread
priorities. So that is a small amount
of additional code. Note: I'm not sure
that I have made the correct assignments concerning Windows thread
priorities. I have assumed the normal
priority class. For this application it
shouldn't make much difference.
The basic flow of the Threads class is the necessary
structure structs and classes and the static variables followed by the
methods. The public Create method is
first. It initializes the threadTable
and creates and starts a timing scheduler thread. Following this is a utility method to create component priorities
to Windows priorities. Then the
TimingScheduler method. For each
component that has been registered, it adds to the threadTable and, if not a
Receive thread, creates the thread. The
start procedure is denoted by lines such as
threadTable.list[i].threadInstance
= new Thread(ComThread2);
where ComThread2 is for the second component thread. Numerous thread procedures are implemented
to have as sufficient number but threads are only created for components that
have been registered.
Each of the thread procedures are similar to
private
static void
ComThread1()
{
ComponentThread(0);
}
doing
nothing but calling the common ComponentThread method passing their index into
the threadTable.
Following the TimingScheduler method is the
ComponentThread method that each of the created component threads will
invoke. It will use separate stack
space to retain the variables for each invocation and each invocation will run
in its own thread. After all the
component threads have started, ComponentThread obtains the main entry point of
the component from the table in the Component class and, if such an entry point
has been provided, checks if the component is to run periodically. If so, it starts a timer for the
thread. In either case it invokes the
component's entry point to start its execution. The component must then do any initialization for its thread and
wait for a continue event. (Of course,
a component could be stand alone and execute without the need to receive
messages and manage its own delays and such so that there would be no need for
it to wait for a PeriodicTimer event or a message received and queued event.)
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
namespace VisualCompiler
{
public class Threads
{
// This framework class has the instances of various
threads. One is a
// higher priority TimingScheduler thread and the others
are threads of
// a pool of threads for components to run in where a
separate thread
// is assigned to each installed component. These component threads
// are assigned the priorities requested by the component
except that
// no component thread is assigned a priority above normal.
public enum ComponentThreadPriority
{
WHATEVER,
HIGHEST,
HIGH,
NORMAL,
LOWER,
LOWEST
};
// Component thread data
private struct ThreadDataType
{
public string name;
public Thread
threadInstance;
public ThreadPriority
priority;
}
// Component thread list
private class ComponentThreadType
{
public int count; // Number of component threads. Note: This should
// end up equal to the number of components.
public
ThreadDataType[] list = new ThreadDataType[Component.MaxComponents];
//
List of component threads
}
// Thread pool of component threads
static private ComponentThreadType threadTable = new ComponentThreadType();
// To allow Remote to be invoked for the correct instance
// of the receive thread.
static int
receiveIndex = 0;
// To allow ComponentThread to idle until all component
threads have started
static bool
allThreadsStarted = false;
// Create the TimingScheduler thread with above normal
priority and start it.
public static void Create()
{
var timingScheduler = new
Thread(TimingScheduler);
timingScheduler.Priority = ThreadPriority.AboveNormal;
// Set the number of threads in the thread pool to the
number of components
threadTable.count = Component.componentTable.count;
// Start the TimingScheduler
timingScheduler.Start();
} // end Create
// Convert component thread priority to that of Windows
private static ThreadPriority ConvertThreadPriority(
Component.ComponentKind
kind,
ComponentThreadPriority priority)
{ // Only for user component threads.
if (kind == Component.ComponentKind.USER)
{
// No component thread is allowed to have a
priority above Normal.
if (priority == ComponentThreadPriority.LOWER)
return ThreadPriority.BelowNormal;
if (priority == ComponentThreadPriority.LOWEST)
return ThreadPriority.Lowest;
return ThreadPriority.Normal;
}
else
{
// Framework
threads are allowed to have their specified priority.
if (priority == ComponentThreadPriority.HIGHEST)
return ThreadPriority.Highest;
if (priority == ComponentThreadPriority.HIGH)
return ThreadPriority.AboveNormal;
if (priority == ComponentThreadPriority.LOWER)
return ThreadPriority.BelowNormal;
if (priority == ComponentThreadPriority.LOWEST)
return ThreadPriority.Lowest;
return ThreadPriority.Normal;
}
} // end ConvertThreadPriority
// The framework TimingScheduler thread
private static void TimingScheduler() //
thread to manage component threads
{
DateTime start = DateTime.Now;
var stopWatch = Stopwatch.StartNew();
// Create the component thread pool/factory; one thread for
each
// component. Wait
until all are created before starting the threads.
if (threadTable.count > 0)
{
int i = 0;
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].name = "ComThread1";
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread1);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 1)
{
int i = 1;
threadTable.list[i].name = "ComThread2";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread2);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 2)
{
int i = 2;
threadTable.list[i].name = "ComThread3";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority
threadPriority;
threadPriority = ConvertThreadPriority(kind,reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread3);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 3)
{
int i = 3;
threadTable.list[i].name = "ComThread4";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind,reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread4);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 4)
{
int i = 4;
threadTable.list[i].name = "ComThread5";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority =
ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread5);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 5)
{
int i = 5;
threadTable.list[i].name = "ComThread6";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread6);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 6)
{
int i = 6;
threadTable.list[i].name = "ComThread7";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind,reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread7);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 7)
{
int i = 7;
threadTable.list[i].name = "ComThread8";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind,reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread8);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 8)
{
int i = 8;
threadTable.list[i].name = "ComThread9";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread9);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if
(threadTable.count > 9)
{
int i = 9;
threadTable.list[i].name = "ComThread10";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread10);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 10)
{
int i = 10;
threadTable.list[i].name = "ComThread11";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread11);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 11)
{
int i = 11;
threadTable.list[i].name = "ComThread12";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if
(kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new Thread(ComThread12);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 12)
{
int
i = 12;
threadTable.list[i].name = "ComThread13";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread13);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
if (threadTable.count > 13)
{
int i = 13;
threadTable.list[i].name = "ComThread14";
Component.ComponentKind
kind =
Component.componentTable.list[i].kind;
ComponentThreadPriority reqPriority =
Component.componentTable.list[i].priority;
ThreadPriority threadPriority;
threadPriority = ConvertThreadPriority(kind, reqPriority);
threadTable.list[i].priority = threadPriority;
if (kind == Component.ComponentKind.RECEIVE)
{
threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
receiveIndex++;
}
else
{
threadTable.list[i].threadInstance = new
Thread(ComThread14);
}
threadTable.list[i].threadInstance.Priority = threadPriority;
}
// Start the created threads of the component thread pool.
for (int tIndex = 0;
tIndex < threadTable.count; tIndex++)
{
string name = Component.componentTable.list[tIndex].name;
Console.WriteLine("TimingScheduler Start {0} {1} {2}",
tIndex,
threadTable.list[tIndex].name,
name);
threadTable.list[tIndex].threadInstance.Start();
}
allThreadsStarted = true;
// Run the TimingScheduler thread every half a second.
// What to have this thread do has yet to be decided.
while (true)
{
// forever loop
Thread.Sleep(500); // one-half second
}
} // end TimingScheduler
// The common component thread code. This code runs in the thread
// of the
invoking component thread. The input
parameter is its
// location in the component table.
// Note: There are multiple "copies" of this
function running; one
// for each
component as called by that component's ComThread.
// Therefore,
the data (such as stopWatch) is on the stack in
// a different
location for each such thread.
private static void ComponentThread(int
location)
{
// Get initial milliseconds; adjust Sleep period to be used
below
Stopwatch stopWatch = new
Stopwatch();
stopWatch.Start();
int cycleInterval = Component.componentTable.list[location].period;
if (cycleInterval < 1) //
no period supplied
{
cycleInterval = 100; // msec
}
int delayInterval = cycleInterval; // initial delay
Disburse queue = Component.componentTable.list[location].queue;
// Wait until all component threads have been started
while (!allThreadsStarted)
{
Thread.Sleep(500);
}
// Create a Timer to signal the periodic components to
resume
// when the timeout has been reached.
Console.WriteLine("Threads ComponentThread {0}",
location);
MainEntry fMain = Component.componentTable.list[location].fMain;
if (fMain != null)
{
// component with periodic entry point
// Create instance of Timer for periodic
components
if (Component.componentTable.list[location].period
> 0)
{
PeriodicTimer executeTimer = new PeriodicTimer(location);
int period = Component.componentTable.list[location].period;
executeTimer.StartTimer(period, period); //
milliseconds
}
// enter the component's callback to await a
resume event
fMain();
}
} // end ComponentThread
// Component thread factory -- one thread for each possible
component.
// Only those for the components in the Component
componentTable will
// be run.
private static void ComThread1()
{
ComponentThread(0);
}
private static void ComThread2()
{
ComponentThread(1);
}
private static void ComThread3()
{
ComponentThread(2);
}
private static void ComThread4()
{
ComponentThread(3);
}
private static void ComThread5()
{
ComponentThread(4);
}
private static void ComThread6()
{
ComponentThread(5);
}
private static void ComThread7()
{
ComponentThread(6);
}
private static void ComThread8()
{
ComponentThread(7);
}
private static void ComThread9()
{
ComponentThread(8);
}
private static void ComThread10()
{
ComponentThread(9);
}
private static void ComThread11()
{
ComponentThread(10);
}
private static void ComThread12()
{
ComponentThread(11);
}
private static void ComThread13()
{
ComponentThread(12);
}
private static void ComThread14()
{
ComponentThread(13);
}
} // end class Threads
// Periodic Component Timer
public class PeriodicTimer
{
private int location;
// index into componentTable
private int
iterations = 0;
Stopwatch stopWatch = new
Stopwatch();
public PeriodicTimer(int
index) // constructor
{
Console.WriteLine("PeriodicTimer
{0}", index);
location = index;
} // end constructor
public void
StartTimer(int dueTime, int period)
{
Timer periodicTimer = new
Timer(new TimerCallback(TimerProcedure));
periodicTimer.Change(dueTime, period);
stopWatch.Start();
}
public void
ResetInvokedTimer()
{
Console.WriteLine("ResetInvokedTimer
entered");
StartTimer(0, 0);
} // end ResetInvokedTimer
private void
TimerProcedure(object state)
{
// The state object is the Timer object.
Timer periodicTimer = (Timer)state;
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
stopWatch.Start();
iterations++;
Console.WriteLine("TimerProcedure
{0} {1} {2}",
Component.componentTable.list[location].name,
ts, iterations);
// Invoke component's Signal.
Component.componentTable.list[location].waitHandle.Set();
} // end TimerProcedure
} // end PeriodicTimer
} // end namespace
Note that in the C# application the Receive threads are created
before Threads is run (via the Create method).
Therefore, if a particular entry into the threadTable is for a receive
thread the thread is not created in Threads since it has already been created. The receive threads are not periodic since
they wait for a message to be received - one thread for each possible remote
application.
Ada Threads package
The Threads package to be shown does not, as yet, concern
itself with the Receive threads since the implementation of the packages
necessary for the remote application have yet to be implemented.
The package spec (visible part) and body (implementation
part) follow. The implementation is
only to create threads for debug/test components so is not complete. The version of the Component package is only
to be able to test.
package Threads is
type
ComponentThreadPriority
is (
WHATEVER,
HIGHEST,
HIGH,
NORMAL,
LOWER,
LOWEST
);
procedure
Create;
end Threads;
with Component;
with ExecItf;
with PeriodicTimer;
with System;
with Text_IO;
with Topic;
with Unchecked_Conversion;
package body Threads is
type
ThreadPriorityType
is (
Lowest,
BelowNormal,
Normal,
AboveNormal,
Highest
);
for
ThreadPriorityType use
(
Lowest => 6,
BelowNormal => 7,
Normal => 8,
AboveNormal => 9,
Highest => 10
);
type
ThreadDataType
is record
Name : String(1..10);
ThreadInstance : ExecItf.HANDLE;
Priority : ThreadPriorityType;
end record;
type
ThreadDataArrayType
is array
(1..Component.MaxComponents) of ThreadDataType;
--
Component thread list
type
ComponentThreadType
is record
Count :
Integer; -- Number of component threads.
Note: This should
-- end up equal to the number of
components.
List : ThreadDataArrayType; -- List of component
threads
end record;
-- Thread
pool of component threads
ThreadTable
: ComponentThreadType;
-- To allow
ComponentThread to idle until all component threads have started
AllThreadsStarted : Boolean := False;
SchedulerThread
-- Handle
returned by Create_Thread for the Scheduler thread
:
ExecItf.HANDLE;
function
to_Void_Ptr is new Unchecked_Conversion
( Source => System.Address,
Target => ExecItf.Void_Ptr );
-- Convert
component thread priority to that of Windows
function
ConvertThreadPriority
( Kind : in Component.ComponentKind;
Priority
: in ComponentThreadPriority
) return
ThreadPriorityType is
use type
Component.ComponentKind;
begin --
Only for user component threads.
if Kind =
Component.USER then
-- No
component thread is allowed to have a priority above Normal.
if
Priority = LOWER then
return BelowNormal;
elsif
Priority = LOWEST then
return Lowest;
else
return Normal;
end if;
else
--
Framework threads are allowed to have their specified priority.
if
Priority = HIGHEST then
return Highest;
elsif
Priority = HIGH then
return AboveNormal;
elsif
Priority = LOWER then
return BelowNormal;
elsif
Priority = LOWEST then
return Lowest;
end if;
return
Normal;
end if;
end
ConvertThreadPriority;
-- Convert
Windows priority to an integer
function
Priority_to_Int
( Priority
: in ThreadPriorityType
) return
Integer is
begin --
Priority_to_Int
case
Priority is
when
Lowest => return 6;
when
BelowNormal => return 7;
when
Normal => return 8;
when
AboveNormal => return 9;
when
Highest => return 10;
when
others => return 8;
end case;
end
Priority_to_Int;
-- The
common component thread code. This code
runs in the thread
-- of the
invoking component thread. The input
parameter is its
-- location
in the component table.
-- Note:
There are multiple "copies" of this function running; one
-- for each component as called by that
component's ComThread.
-- Therefore, the data (such as stopWatch)
is on the stack in
-- a different location for each such
thread.
procedure
ComponentThread
( Location
: in Integer
) is
CycleInterval : Integer;
DelayInterval : Integer;
Callback : Topic.CallbackType;
Period : Integer;
use type
Topic.CallbackType;
begin --
ComponentThread
CycleInterval := Component.ComponentTable.List(Location).Period;
if
CycleInterval < 1 then -- no period supplied
CycleInterval
:= 500; -- msec
end if;
DelayInterval := CycleInterval; -- initial delay
-- Wait
until all component threads have been started
while
AllThreadsStarted = False loop
Delay(Duration(DelayInterval/100)); -- using Periods in seconds
end loop;
-- Create
a Timer to signal the periodic components to resume
-- when
the timeout has been reached.
Text_IO.Put_Line("Threads ComponentThread ");
Callback
:= Component.ComponentTable.List(Location).fMain;
if
Callback /= null then -- component with periodic entry point
--
Create instance of Timer for periodic components
if
Component.ComponentTable.List(Location).Period > 0 then
--
Instantiate PeriodicTimer for this component
declare
package ExecuteTimer is new
PeriodicTimer( Index => Location );
begin
Period := Component.ComponentTable.List(Location).Period;
ExecuteTimer.StartTimer
(
DueTime => Period,
Period => Period, -- milliseconds
Priority => 8, -- Normal
Wait =>
Component.ComponentTable.List(Location).WaitEvent );
end;
end if;
--
Enter the component's callback to await a resume event from Timer or
-- from
receive of a message.
Callback( TopicId => Topic.Empty );
end if;
Delay(Duration(DelayInterval/100)); -- using Periods in seconds
end
ComponentThread;
--
Component thread factory -- one thread for each possible component.
-- Only
those for the components in the Component componentTable will
-- be run.
procedure
ComThread1 is
begin
ComponentThread(1);
end
ComThread1;
procedure
ComThread2 is
begin
ComponentThread(2);
end
ComThread2;
-- The
TimingScheduler thread
procedure
TimingScheduler is -- thread to manage component threads
I : Integer;
Kind :
Component.ComponentKind;
ReqPriority :
ComponentThreadPriority;
ThreadPriority : ThreadPriorityType;
begin --
TimingScheduler
-- Create
the component thread pool/factory; one thread for each
--
component.
if
ThreadTable.Count > 0 then
I := 1;
Kind :=
Component.ComponentTable.List(I).Kind;
ReqPriority := Component.ComponentTable.List(I).Priority;
ThreadPriority := ConvertThreadPriority(Kind, ReqPriority);
ThreadTable.List(I).Name := "ComThread1";
ThreadTable.List(I).Priority := ThreadPriority;
ThreadTable.List(I).ThreadInstance :=
ExecItf.Create_Thread
(
Start => ComThread1'Address,
Parameters => to_Void_Ptr(System.Null_Address),
Stack_Size => 0,
Priority =>
Priority_to_Int(ThreadPriority) );
end if;
if
ThreadTable.Count > 1 then
I := 2;
Kind :=
Component.ComponentTable.List(I).Kind;
ReqPriority := Component.ComponentTable.List(I).Priority;
ThreadPriority := ConvertThreadPriority(Kind, ReqPriority);
ThreadTable.List(I).Name := "ComThread2";
ThreadTable.List(I).Priority := ThreadPriority;
ThreadTable.List(I).ThreadInstance :=
ExecItf.Create_Thread
(
Start => ComThread2'Address,
Parameters => to_Void_Ptr(System.Null_Address),
Stack_Size => 0,
Priority =>
Priority_to_Int(ThreadPriority) );
end if;
AllThreadsStarted := True; -- since started
as created
while
True loop -- forever
Delay
10.0;
end loop;
end
TimingScheduler;
procedure
Create is
begin --
Create
ThreadTable.Count := Component.ComponentTable.Count;
SchedulerThread
:= ExecItf.Create_Thread
( Start
=> TimingScheduler'address,
Parameters =>
to_Void_Ptr(System.Null_Address),
Stack_Size => 0,
Priority
=> Priority_to_Int(Normal) ); -- use AboveNormal? );
end Create;
end Threads;
Note how the Ada Threads package follows the C# class except
for the reordering of its procedures from the methods of the C# class and the
much reduced set of component threads since only accommodating the debug/test
case. Hidden in ExecItf is the
interface to the C libraries of GNAT to interface to Windows.
Ada PeriodicTimer package
This package is an example of an Ada generic. It works somewhat similar to "new"
in instantiating a C class. That is,
there can be multiple instances of the package. With PeriodicTimer this is necessary since there needs to be a
timer for each periodic thread in order to notify the thread's component when
it needs to execute.
In order for the ComponentThread procedure of the Threads
package to both start the timer and enter the component's callback each timer
has to run in its own separate thread.
This is because ComponentThread is running in the component's
thread. When the timer is started there
won't be a return to the component's thread and when the component's callback
is entered, it won't be returning to ComponentThread of the Threads
package. Therefore, each timer needs to
have its own thread. Therefore,
ComponentThread passed what the new thread's priority should be to timer's
StartTimer procedure.
When a component registers itself, the code creates a
WaitEvent handle for the component and returns it to the component for its
use. This handle is also passed to
PeriodicTimer when ComponentThread invokes StartTimer to create the timer so
that it can signal the component when to continue.
The public/visible part
with ExecItf;
generic
-- generic PeriodicTimer
Index
--
Instantiation index
: Integer;
package PeriodicTimer is
procedure
StartTimer
(
DueTime : in Integer;
Period : in Integer;
Priority
: in Integer;
Wait : in ExecItf.HANDLE
);
end PeriodicTimer;
The implementation part
with ExecItf;
with System;
with Unchecked_Conversion;
package body PeriodicTimer is
-- Handles
of a particular timer
TimerThread
: ExecItf.HANDLE;
TimerHandle
: ExecItf.HANDLE;
WaitHandle : ExecItf.HANDLE;
procedure
TimerProcedure is
InfiniteValue : Integer := -1;
Result :
Boolean;
Rtrn : ExecItf.WaitReturnType;
begin --
TimerProcedure
while
(True) loop
Rtrn :=
ExecItf.WaitForSingleObject(TimerHandle,InfiniteValue);
Result
:= ExecItf.Set_Event( Event => WaitHandle );
end loop;
end
TimerProcedure;
procedure
StartTimer
( DueTime
: in Integer;
Period : in Integer;
Priority
: in Integer;
Wait : in ExecItf.HANDLE
) is
Rtrn :
Boolean;
function
to_Void_Ptr is new Unchecked_Conversion
( Source => System.Address,
Target =>
ExecItf.Void_Ptr );
begin --
StartTimer
WaitHandle := Wait;
TimerThread := ExecItf.Create_Thread
( Start =>
TimerProcedure'Address,
Parameters => to_Void_Ptr(System.Null_Address),
Stack_Size => 0,
Priority => Priority );
TimerHandle := ExecItf.CreateWaitableTimer( ManualReset => False );
Rtrn :=
ExecItf.SetWaitableTimer
(
Timer => TimerHandle,
DueTime => DueTime,
Period => Period,
Resume => False );
end
StartTimer;
end PeriodicTimer;
Each instance of the PeriodicTimer package has its own copy
of the data such as the static data
TimerThread
: ExecItf.HANDLE;
TimerHandle
: ExecItf.HANDLE;
WaitHandle : ExecItf.HANDLE;
Of course, like anything else, the stack data declared
inside a procedure or function is only available while inside the routine. Therefore, the TimerThread handle, for
instance, is different for the timer used with the first component from that
used for the second.
StartTimer is passed the Wait handle which is saved in the
static data for use by the TimerProcedure.
The same for the timer handle that is returned from the create of a
waitable timer and used to set the timer.
The created thread for the timer starts immediately entering
the TimerProcedure. The TimerProcedure
loops forever waiting for the timer to trip.
When it does it sends the component's wait event to signal the component
to continue.
A portion of a sample test component is
procedure
Main -- callback
( Topic :
in Boolean := False
) is
WaitResult :
ExecItf.WaitReturnType;
ResetResult : Boolean;
begin --
Main
Text_IO.Put_Line("in Component1 callback");
loop --
wait for event
WaitResult :=
ExecItf.WaitForSingleObject(Result.Event, -1);
ResetResult := ExecItf.Reset_Event(Result.Event);
end
loop;
end Main;
The callback was entered when the Threads package
ComponentThread procedure invoked it.
It then enters its forever loop and does a wait for its event. When the wait is satisfied – that is, the
PeriodicTimer TimerProcedure invokes Set_Event the Reset_Event function is
invoked to reset the wait so that the next wait will not immediately return.
An actual component would do something in the loop after the
wait was satisfied. In the debug
version, the current time is obtained and the seconds value is output with an
identifier of C1 or C2 depending upon which test component it is. The same is done in the PeriodicTimer when
the timer has tripped with an identifier of T1 or T2 depending upon which
instance of the timer has tripped.
A sample of this output is
Threads ComponentThread
in TimerProcedure for 2
in Component2 callback
T2 34
C2 34
Threads ComponentThread
in TimerProcedure for 1
in Component1 callback
T1 36
C1 36
T2 38
T1 38
C2 38
C1 38
T1 40
C1 40
T1 42
C1 42
T2 44
T1 44
C1 44
C2 44
T1 46
C1 46
T1 48
C1 48
T2 50
C2 50
T1 50
C1 50
T1 52
C1 52
T1 54
C1 54
The "in Component1/2 callback" output is when
Threads invoked the callback as shown in the first line of the Main
procedure. Then the T1 and C1 pairs,
for instance, show that the timer tripped at 36 seconds and the component had
its wait satisfied in the same second.
It can be observed that the first component (C1) receives a wait event
signal at 2 second intervals and that the second component (C2) receives its
signals at 6 second intervals. These
are the periodic intervals that were specified when the dummy components were
registered with the Component package.
No comments:
Post a Comment