Reputation: 163
I am doing some Ada this Sunday .. ;-)
I have write a small Log package:
log.ads:
package Log is
procedure log (text: String);
end Log;
log.adb:
with Ada.Text_IO;
package body Log is
procedure log (text: String) is
begin
Ada.Text_IO.Put (text);
end log;
end Log;
I can use it like this: test.adb:
with Log;
procedure Test is
begin
Log.log ("bla bla");
end Test;
Now, I would like to "improve" this package. I would like the log procedure to "push" the text to a task. It is the task that does the "Ada.Text_IO.Put (text)". The task can be:
task Logger_Task is
entry log (text : String);
end Logger_Task;
task body Logger_Task is
begin
loop
accept log (text: String) do
Ada.Text_IO.Put (text);
end log;
end loop;
end Logger_Task;
I would like that the Log clients are not aware of this task, so it should be hidden somewhere into the Log package. I do not know how and where to instantiate the task ...
This task must also remain active throughout the duration of the application.
Thanks for your help.
Upvotes: 4
Views: 356
Reputation: 3641
A method using rendezvous was shown by another user, but if the means of handling that message could block, then even if you do that outside the accept statement, the caller could still potentially block waiting on the logger task to get back around to the accept statement. Text_IO should not be much of a problem, but for other types of outputs that potentially could block longer, consider an alternative:
Use an unbounded synchronized queue paired with a standalone task that monitors the queue and retrieves messages from it as they are added and it has the time. Even though it is an entry, the Enqueue operation will not block the calling task. This way, if the output method for your logger ends up blocking for relatively long periods of time occasionally, your other tasks will just pop messages on a queue and move on and the monitoring task will eventually output them when it catches up.
logging.ads
package Logging is
procedure Put_Line(Message : String);
end Logging;
logging.adb
with Ada.Containers.Synchronized_Queue_Interfaces;
with Ada.Containers.Unbounded_Synchronized_Queues;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
use Ada.Containers;
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
package body Logging is
-- Create the queue
package Interfaces is new Synchronized_Queue_Interfaces(Unbounded_String);
package Queues is new Unbounded_Synchronized_Queues(Interfaces);
Messages : Queues.Queue;
-- This task will monitor the queue until the program ends
task Logger;
task body Logger is
Message : Unbounded_String := Null_Unbounded_String;
begin
-- Stop looping once both main ends and there are no messages left
-- The Environment_Task refers to the task that runs Main
while Is_Callable(Environment_Task) or Messages.Current_Use > 0 loop
-- Check the queue for a message but time out if not. We
-- need the timeout because Dequeue can block if the queue
-- is empty.
select
Messages.Dequeue(Message);
Ada.Text_IO.Unbounded_IO.Put_Line(Message);
or
-- Pick a timeout that makes sense. Longer timeouts
-- give better CPU usage during idle times, but also means
-- the program might not end immediately. 10ms or 1 second
-- might be fine for general use
delay 1.0;
end select;
end loop;
Ada.Text_IO.Put_Line("Main task closed, closing Logger");
end Logger;
procedure Put_Line(Message : String) is
begin
Messages.Enqueue(To_Unbounded_String(Message));
end Put_Line;
end Logging;
Upvotes: 1
Reputation: 39808
If you define the task in the body of the package, you can keep the procedure log
interface:
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;
package body Log is
task Logger_Task is
entry log (text : String);
end Logger_Task;
task body Logger_Task is
Cache: Ada.Strings.Unbounded.Unbounded_String;
begin
loop
select
accept log (text: String) do
Cache := Ada.Strings.Unbounded.To_Unbounded_String (text);
end log;
Ada.Text_IO.Unbounded_IO.Put (Cache);
or
terminate;
end select;
end loop;
end Logger_Task;
procedure log (text: String) is
begin
Logger_Task.log (text);
end log;
end Log;
select … or terminate;
is vital to end the task when the main task ends (this alternative will only be taken when the main task has reached its end).
Cache
is also important because it allows the calling task to continue after the text
parameter has been received by the accept
block. Directly calling Put
in the accept block will make the calling task wait for it to finish, since it only continues after the accept
block has been left.
Upvotes: 5
Reputation: 6430
You already instantiated it.
task Logger_Task is
entry log (text : String);
end Logger_Task;
is the same as creating an instance of an anonymous task type
:
task type Anonymous is
entry log (text : String);
end Anonymous;
Logger_Task : Anonymous;
Upvotes: 5