I need some advice regarding a coding requirement that may suggest the
need for an object oriented solution. The code fragments presented
here attempt to solve the problem in C.
The problem:
Data Packets are received from a socket and consist of 3 elements: the
Packet Type, the "Item", and some Data.
The Packet type represents either:
(1) A Task to be performed on the Item using the supplied Data
(2) A Condition that has arisen for the Item
(3) A signal to exit
A Task triggers a function that executes code based on the Packet's
Item and Data. Often the Task will trigger, and must wait for,
additional Packets to satisfy certain Conditions before the Task can
complete. The number of Conditions and the length of time spent
waiting will vary for each Task. In this example, there is a "simple"
Task that waits for one condition and a "complex" Task that waits for
2 conditions at different points of processing. In the actual
implementation, there would be many more Tasks, some with 3 or more
Conditions.
Listing 1 represents what I know will not work but illustrates the
problem. Listing 2 represents the best way I've come up with to handle
this in C.
Is Listing 2 a sound approach?
Is there a better way in C?
Would it be much easier in C++ or another "object oriented" language?
Thanks a million!
-Kerry
----------
Listing 1:
----------
As Packets are received, the appropriate function is called for
processing. During processing, additional Packets are received from
within the functions so that the Task can wait for the appropriate
condition(s) and Data before continuing the Task.
A fundamental problem with this approach is that while a function is
waiting for additional Packets to indicate the desired Condition, a
Packet may arrive for some other Item. Handling the new Packet within
the function seems impossible - what if the desired Condition for the
first Packet arrives during the processing of the second?
I could store incoming Packets that I'm not ready for and process them
after the pending Task has completed but that could lead to
"deadlocks" if the new Packet was meant to supercede the first. At
best, the new Tasks would be unnecessarily delayed since they'd have
to wait for the pending Task's Conditions to be met.
// Define Packet Structure
struct stx
{ int ptype;
int item;
int data;
};
// Define Packet Types
int DoSimpleTask = 0;
int DoComplexTask = 1;
int DoOtherTask = 2;
int ConditionA = 3;
int ConditionB = 4;
int ConditionC = 5;
int DoExit = 6;
struct stx x;
..
..
..
do
{
x = BlockForNextPacket();
switch (x.ptype)
{
DoSimpleTask: rc = funcDoSimpleTask(x.item, x.data);
break;
DoComplexTask: rc = funcDoComplexTask(x.item, x.data);
break;
DoOtherTask: rc = funcDoOtherTask(x.item, x.data);
break;
DoExit: rc = funcExit();
break;
default: break;
}
}
while (x.ptype != DoExit)
..
..
..
int funcDoSimpleTask(int i, int d)
{
// Perform Task here
// Wait until Condition A for this item exists
do
{ x = BlockForNextPacket(); }
while ( (x.item != i) || (x.ptype != ConditionA) )
// Perform post-Task processing here
return 0;
}
int funcDoComplexTask(int i, int d)
{
struct stx x;
// Perform 1st part of Task here
// Wait until Condition A for this Item exists
do
{ x = BlockForNextPacket(); }
while ( (x.item != i) || (x.ptype != ConditionA) )
// Perform 2nd part of Task here
// Wait until Condition B for this Item exists
do
{ x = BlockForNextPacket(); }
while ( (x.item != i) || (x.ptype != ConditionB) )
// Perform post-Task processing here
return 0;
}
int funcDoOtherTask(int i, int d)
{
// Perform complete Task here and exit immediately
return 0;
}
----------
Listing 2:
----------
All Packets are received within a single loop. Tasks have been broken
up into fragments so that none of them require fetching additional
Packets. Since certain Packets (Condition A for instance) can mean
different things at different times, an array has been added to
maintain the State of incomplete Tasks. While all Tasks are handled
correctly, the overhead needed to maintain State is confusing, even in
this simplistic example.
Although it's not handled here for the sake of brievity, if a new Task
arrives for an Item that is already in a pending State, either the new
or old Task would need to be aborted. Identifying and handling these
situations seems possible since the State arrays are in scope when the
new Task arrives.
..
..
..
int SimpleTaskState[number-of-items];
int ComplexTaskState[number-of-items];
do
{
x = BlockForNextPacket();
switch (x.ptype)
{
DoSimpleTask: { rc = funcDoSimpleTask(x.item, x.data);
// Update State - waiting for Cond A
SimpleTaskState[x.item] = 1;
break;
}
DoComplexTask: { rc = funcDoComplexTask(x.item, x.data);
// Update State - waiting for Cond A
ComplexTaskState[x.item] = 1;
break;
}
DoOtherTask: { rc = funcDoOtherTask(x.item, x.data);
break;
}
DoExit: { rc = funcExit();
break;
}
ConditionA: { if ( SimpleTaskState[x.item] == 1 )
{ // Simple Task was waiting for Cond A
// Now finish the Task
rc = funcDoSimplePost(x.item, x.data);
// Task complete, reset State
SimpleTaskState[x.item] = 0;
}
else if ( ComplexTaskState[x.item] == 1 )
{ // Complex Task was waiting for Cond A
// Now do part 2 of Task
rc = funcDoComplexPart2(x.item, x.data);
// Part 2 complete, now waiting for Cond B
ComplexTaskState[x.item] = 2;
}
else
{ // Condition A is irrelevant right now
}
break;
}
ConditionB: { if ( ComplexTaskState[x.item] == 2 )
{ // Complex Task was waiting for Cond B
rc = funcDoComplexPost(x.item, x.data);
// Task complete, reset state
ComplexTaskState[x.item] = 0;
}
else
{ // Condition B is irrelevant right now
}
break;
}
default: break;
}
}
while (x.ptype != DoExit)
..
..
..
int funcDoSimpleTask(int i, int j)
{
// Perform Task here
return 0;
}
int funcDoSimplePost(int i, int j)
{
// Perform post-Task processing here
return 0;
}
int funcDoComplexTask(int i, int j)
{
// Perform 1st part of Task here
return 0;
}
int funcDoComplexPart2(int i, int j)
{
// Perform 2nd part of Task here
return 0;
}
int funcDoComplexPost(int i, int j)
{
// Perform post-Task processing here
return 0;
}
int funcDoOtherTask(int i, int j)
{
// Perform complete Task here and exit immediately
return 0;
}