How to have a background task (GNAT, Windows)

Here’s the thing, the rendezvous is synchronization-for-data-transfer.
What your mental model is saying is that the rendezvous (acceptend) is the [sub]task… the mismatch here will cause troubles for you. — I would recommend using a task to implement some protocol a few times to disabuse that notion and train yourself on how they actually work.

Example, given a process where you need a client doing (A, then B [same client]) or C, repeatedly would be done with:

Task Example is
  Entry A;
  Entry B;
  Entry C;
  Entry Done;
End Example;

Task Body Example is
  Finished : Boolean := False;
Begin
  Loop
    Exit when Finished;
    Select
        Accept A;
        Accept B;
    or
        Accept C;
    or
        Accept Done;
        Finished:= True;
    End Select;
  End Loop;
End Example;

The above implements the above-stated protocol — with the addition of Done for signaling shutdown. (Note, the reason that A then B works here is because of the unstated assumption that client code will call entry-A, then entry-B, not ‘randomly’ spamming A or B.)

Now, continuing on (after you’ve implemented a few sequence-protocols, let’s add in data-transfer:

Task Example_2 is
  Entry Put( Value : in            Natural );
  Entry Get( Value :    out access Natural );
End Example_2;

Task Body Example_2 is
  Has_Value : Boolean := False;
  The_Value : Natural;
Begin
  Loop
    Select
        accept Put( Value : in Natural ) do
            Has_Value:= True;
            The_Value:= Value;
        end Put;
    or
        accept Get( Value :    out access Natural ) do
            Value:= (if Has_Value then 
                        New Natural'( The_Value ) 
                     else Null);
            Has_Value:= False;
        end Get;
    End Select;
  End Loop;
End Example_2;

And, if you’re going to be using unconstrained types, like String, you’re going to have to use a by-parts approach:

Task Example_3 is
  Entry Put( Value : in     String  );
  Entry Get( Value :    out Natural );
  Entry Get( Value :    out String  );
End Example_3;

Task Body Example_3 is
  Package Word_List is new Ada.Containers.Indefinite_Vectors
  ( Element_Type => String, Index_Type => Positive );

  Items : Word_List.Vector;
  Index : Natural:= Natural'First;
Begin
  Loop
    Select
      accept Get( Value :    out Natural ) do
        Value:= (if Index not in 1..Items.Length then 0 else Items(Index)'Length);
      end Get;
      accept Get( Value :    out String  ) do
        Value:= (if Index not in Positive then "" else Items(Index));
      end Get;
    or
      accept  Put( Value : in     String  ) do
        Items.Append( Value );
        Index:= 
      end Put;
    End Select;
  End Loop;
End Example_3;

Function Example_Usage return String is
  Length : Natural;
Begin
  -- In order to retrieve the value, we need two rendezvous:
  -- First, to retrieve the length; and after generating a string of that length,
  -- Second, to populate that string with that data.
  Example_3.Get( Length );
  Return Result : String(1..Length) do
    Example_3.Get( Result );
  End return;
End Example_Usage;

The reason that you need to split things this way is because, with an out parameter (or in out) you cannot [re-]set the length of said parameter; to work around this, you get the length you need, then pass in the appropriately-sized array. — For explanations on why you have to do this, see my posts on the threads on strings/string-initialization: here and here, but the TL;DR of it is this: given a constrained value, you cannot re-set the constraints thereon; you may return results of an unconstrained subtype because the value thereof provides the constraints thereon.

1 Like