Cooperating Sequential Tasks

  1. Introduction
    1. Arduino
    2. Interrupts
  2. Simple Tasks
  3. Multiple Tasks
  4. Communicating Tasks
  5. Communicating Values
  6. Synchronising Tasks
  7. Buffered Communication
  8. Multiple Senders
  9. Conditional Tasks
  10. Transput
  11. Implementation
    1. Common Data
    2. Tasks
    3. task_builder
    4. task
    5. channel
    6. virtual_machine
    7. set
    8. queue
    9. clock
    10. standard
    11. Adjusting Limits


Data Structure

Every time new_channel is called through the Tasks interface, a channel structure, as defined below, is added to the channels array (See Common Data).

struct channel
  set  receivers;
  set  active_senders;
  set  active_receivers;
  int  next_sender;

The individual fields are described below.

set receivers;

When the task_builder encounters a receive_from action, in addition to adding the appropriate values to the code array, it also adds the task identifier of the receiver task to the to the receivers field of the specified channel. This is used to by the channel_ready function to check that all the tasks which receive from a channel are in a receiving state before a communication can be completed. This is required because many tasks can receive a signal from a channel at the same time. No equivalent set of senders is required, because only one task can be the source of a signal at any one time.

set  active_senders;
set  active_receivers;

These two fields are used to keep track of which tasks are currently waiting to communicate via a channel, and are used by the implementation of the channel_ready function. When a task executes a send_to or transput action its task identifier is added to the active_senders set of the channel concerned. When a task executes a receive_from or transput action, its task identifier is added to the active_receivers set of the channel concerned. When a communication completes, the identifier of the sender involved is removed from the active_senders set, and the active_receivers set is cleared.

int next_sender;

Where multiple simultaneous senders are available to a channel when all the receivers are ready, the next_sender field is used to choose which sender should be used to complete the communication. The value of next_sender is scanned cyclically through the active_senders set in an attempt to ensure that some tasks do not dominate communications, e.g. if tasks with identifiers 3 and 5 are both ready to send, the selection process might first choose task 3 as a sender, and then choose task 5 next time even if task 3 becomes ready to send again by the time all the receivers are ready again. This mechanism only has any effect if tasks 3 and 5 can become ready to receive at the same time. If sends from tasks 3 and 5 never overlap, the first available sender is used regardless.


bool channel_ready(channel* c);

The channel_ready function determines whether a channel is ready to communicate. For this to be the case, it must have at least one active_sender and its set of active_receivers must contain the full set of receivers established statically by the task_builder.

void send_data_to(set* receivers, int value);

The send_data_to function is used to send a value to all of the tasks in a set of receivers. It does this by calling the sink function for each task in the set with the value as a parameter.

void send_signal   (task_record* t, byte channel_id, data_source source);
void receive_signal(task_record* t, byte channel_id, data_sink   sink  );

There a two ways that a channel can become ready to communicate. Either all of the receivers are ready at the point a sender executes a send_to or transput action. Alternatively, a sender and all but one of the required receivers are ready at the point the final receiver executes a receive_from or transput action. In the first case it is the virtual machine's implementation of send_to or transput which completes the communication and the send_signal function performs all the necessary updates. In the second case it is the virtual machine's implementation of receive_from or transput which completes the communication and the receive_signal function performs all the necessary updates.

Two different functions are required because the order of events is different, i.e. when a send_to action completes communication the sender task remains active and all of the receivers must be reactivated, but when a receiver_from action completes communication the receiver responsible remains active and the sender and all the other receivers (if any) must be reactivated.

byte select_sender(channel* c);

This function selects a sender task to complete a communication when more than one active sender is available to a channel at the point all the required receivers are ready. It does so cyclically as described above in the description of the next_sender field.

void clear_alt_input  (task_record* t);
void clear_alt_output (task_record* t);
void clear_alt_outputs(set* receivers);
void clear_alt_inputs (set* receivers);

A transput action executed by a task can result in the task sending on one channel, or receiving on another, but not both. If the transput is completed by sending, all of the information set up to permit receiving must be cleared. If the transput is completed by receiving, all of the information set up to permit sending must be cleared. These four functions are used by the virtual_machine in various combinations to achieve this.