Ada - 如何实现一个允许主线程轮询的异步任务?

Ada - How to implement an asynchronous task that allows the main thread to poll it?

我想创建一个任务,在主线程做其他事情的同时从文件中读取几分钟。但我希望主线程能够在不阻塞主线程的情况下轮询任务以查看它是否为 "Busy"(布尔值)。

我在这里做了一个幼稚的尝试,它确实有效,但它让 Busy 标志完全暴露在主线程随意切换的状态(这不安全)...

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

  task type Non_Blocking_Reader_Task (Busy : access Boolean) is
    entry Read (Destination : in Natural);
  end Non_Blocking_Reader_Task;

  task body Non_Blocking_Reader_Task is
  begin
    loop
      select
        when not Busy.all =>
          accept Read (Destination : in Natural) do
            Busy.all := True;
          end Read;

          for i in 1 .. 50 loop
            Put ("."); --  pretend to do something useful
            delay 0.1; --  while wasting time
          end loop;

          Busy.all := False;
      end select;
    end loop;
  end Non_Blocking_Reader_Task;

  Reader_Busy_Volatile : aliased Boolean;
  Reader : Non_Blocking_Reader_Task (Reader_Busy_Volatile'Access);
begin

  Put_Line (Reader_Busy_Volatile'Image);

  Reader.Read (123);

  for i in 1 .. 15 loop
    Put_Line (Reader_Busy_Volatile'Image);
    delay 0.5;
  end loop;

  abort Reader;
end Main;

我的第二个想法是创建一个 protected type 并将标志和任务隐藏在其中,但语言不允许这样做。

问题

我如何创建一个受保护的 "task is busy" 标志,它可以从主线程只读并且 read/write 从任务(不会导致主线程阻塞)?


编辑:

The solution!

我根据@flyx 的斯特林建议修改(可行)的解决方案:)

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   task type Reader_Task is
      entry Read (Destination : in Natural);
      entry Join;
      entry Ready;
   end Reader_Task;

   task body Reader_Task is
      Dest : Natural;
   begin
      loop
         select
            accept Read (Destination : in Natural) do
               Dest := Destination;
            end Read;

            --  we only get here after a Read has been received.
            for i in 1 .. 5 loop
               Put ("."); --  pretend to do something useful
               delay 1.0; --  while wasting time
            end loop;
         or
            accept Join;
         or
            accept Ready;
         or
            terminate;
         end select;
      end loop;
   end Reader_Task;

   Reader : Reader_Task;
begin
   --  first call will not block.
   Reader.Read (123);
   Put_Line ("MAIN: Reading in progress on second thread");

   for i in 1 .. 12 loop
      select
         --  NON-BLOCKING CALL!
         Reader.Ready; -- test if task is busy
         Put_Line ("MAIN: NON-BLOCKING CALL          SUCCEEDED -- TASK IS NOT BUSY");
      else
         Put_Line ("MAIN: NON-BLOCKING CALL FAILED             -- TASK IS BUSY");
      end select;

      delay 1.0;
   end loop;

   Put_Line ("Main: Waiting for Reader (BLOCKING CALL)...");
   Reader.Join;
   Put_Line ("Main: all finished!");
end Main;

我在任务中又添加了两个条目:JoinReady,它们基本上相同,只是名称不同。 Join提醒我对其进行阻塞调用,Ready表示非阻塞调用适合测试任务可用性。我这样做是因为有时我想知道 Read() 的前一个 运行 是否已完成而不触发新的。这让我可以干净利落地完成这一切,完全没有任何离散的标志!厉害了。

在 Ada 中,调用者 决定入口调用是否阻塞。您不应该尝试在任务中实现代码来检查它。

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   task type Reader_Task is
      entry Read (Destination : in Natural);
   end Reader_Task;

   task body Reader_Task is
   begin
      loop
         select
            accept Read (Destination : in Natural) do
               null;
               -- store Destination (?)
            end Read;
         or
            -- allow task to be terminated while waiting
            terminate;
         end select;

         --  we only get here after a Read has been received.
         for i in 1 .. 50 loop
            Put ("."); --  pretend to do something useful
            delay 0.1; --  while wasting time
         end loop;
      end loop;
   end Reader_Task;

   Reader : Reader_Task;
begin
   --  first call will not block.
   Reader.Read (123);

   for i in 1 .. 15 loop
      --  check whether Read can be called immediately and if yes,
      --  call it.
      select
         Reader.Read (456);
      else
         -- Read is not available, do something else.
         null;
      end select;

      delay 0.5;
   end loop;

   -- don't call abort; let Reader finish its current task.
   -- Reader will be terminated once it waits on the terminate alternative
   -- since the parent is finished.
end Main;

select … else … end select 结构是 Ada 进行非阻塞调用的方式。您不使用标志来表示任务已准备好接收条目调用,因为此状态可能会在标志查询和条目的实际调用之间发生变化。 select 旨在避免此问题。