任务:响应非常慢

Tasking: Very slow response

我的实际程序创建了一个任务,但我没有按应有的方式对控制消息做出反应。由于它已经变得相当大,我提供了一个具有相同控制逻辑的简短测试程序。它创建一个后台任务,每 0.1 秒重复一个循环。根据受保护的标志“运行”,它会打印出 'a',或者什么都不做。当我设置“运行”时,程序立即关闭,打印 'a's。但是当我设置“stopreq”时,它需要几秒钟,有时甚至超过 10 秒,直到它停止。我希望响应时间为 0.1 或 0.2 秒。

有人有解释和解决办法吗?

我的主程序打开一个带有 3 个按钮的 window:“开始”调用下面的子程序“开始”,“停止”调用 Request_Stop,“退出”调用 Request_Quit.我正在使用 PC 运行 Linux.

这是我的任务包的主体。如果您需要更多,请告诉我,我 post 其他部分。

with Ada.Text_IO;
with Ada.Calendar;

package body Tasking is
   
   t_step:  constant Duration:= 0.1;
   dti:     Duration;
      
   protected Sync is
      procedure Start;              -- sim. shall start
      procedure Request_Stop;           -- sim. shall stop
      procedure Stop_If_Req;
      function Is_Running return Boolean;   -- sim. is running
      procedure Request_Quit;           -- sim.task shall exit
      function Quit_Requested return Boolean;   -- quit has been requested
      procedure Reset_Time;
      procedure Increment_Time (dt: Duration);
      procedure Delay_Until;
   private
      running:  Boolean:= false;
      stopreq:  Boolean:= false;
      quitreq:  Boolean:= false;
      ti:   Ada.Calendar.Time;
   end Sync;
   
   protected body Sync is
      
      procedure Start is begin running:= true; end Start;
      
      procedure Request_Stop is
      begin
     if running then stopreq:= true; end if;
      end Request_Stop;
      
      procedure Stop_If_Req is
      begin
     if stopreq then
        running:= false;
        stopreq:= false;
     end if;
      end Stop_If_Req;
      
      function Is_Running return Boolean is begin return running; end Is_Running;
      
      procedure Request_Quit is begin quitreq:= true; end Request_Quit;
      
      function Quit_Requested return Boolean
      is begin return quitreq; end Quit_Requested;
      
      procedure Reset_Time is begin ti:= Ada.Calendar.Clock; end Reset_Time;
      
      procedure Increment_Time (dt: Duration) is
      begin
     ti:= Ada.Calendar."+"(ti, dt);
     dti:= dt;
      end Increment_Time;
      
      procedure Delay_Until is
     use type Ada.Calendar.Time;
     now:   Ada.Calendar.Time;
      begin
     now:= Ada.Calendar.Clock;
     while ti < now loop    -- while time over
        ti:= ti + dti;
     end loop;
     delay until ti;
      end Delay_Until;
      
   end Sync;
   
   
   task body Thread is
   begin
      Ada.Text_IO.Put_Line("starting task");
      while not Sync.Quit_Requested loop
     if sync.Is_Running then
        sync.Increment_Time (t_step);
        sync.Delay_Until;
        Ada.Text_IO.Put("a");
        sync.Stop_If_Req;
     else
        delay t_step;
        sync.Reset_Time;
     end if;
       end loop;
   end Thread;
   
   
   procedure Start is
   begin
      Sync.Start;
   end Start;
   
   function Is_Running return Boolean is
   begin
      return Sync.Is_Running;
   end Is_Running;
   
   procedure Request_Stop is
   begin
      Ada.Text_IO.Put_Line("");
      Sync.Request_Stop;
   end Request_Stop;
   
   procedure Request_Quit is
   begin
      Sync.Request_Quit;
   end Request_Quit;
   
end Tasking;

你的代码描述和注释太差,我无法理解你想要它做什么。

但是在受保护的操作 (Sync.Delay_Until) 中使用“delay until”语句是不正确的——这是一个“有界错误”。如果有效,它可能会阻止对该受保护对象的所有其他调用,直到延迟期满。我建议您在尝试更正代码时应该从那里开始。

感谢您的评论,该程序现在可以正常运行。

致 Jesper Quorning:

“过去的时间”在这里不是问题,但在我的实际软件项目中可能是这样。尽管如此,我还是在下面的更正版本中包含了一个补救措施,因此它可以作为其他人的模式。谢谢提示。

致尼克拉斯·霍尔斯蒂:

我将“delay until”从受保护的操作中移出,程序现在按预期运行,感谢您的解释。 (有时一个运行不好的程序比一个根本不运行的程序更糟糕,因为在后一种情况下你更认真地对待编译器警告。)

我想做的是:

  • 以精确定义的时间间隔执行应用程序
  • 能够enable/disable随时
  • 仅在明确定义的状态下才给它时间停止
  • 知道了,什么时候真的停止了
  • 整个事情的受控终止。

我的实现思路是:

  • 将应用程序作为单独的任务无限循环调用
  • 检查循环顶部是否请求退出
  • 如果启用则执行应用程序的 if 语句
  • 否则简单延迟。 来自其他包的控制是通过 public 子程序完成的 开始,Is_Running、Request_Stop、Request_Quit。 除了上面提到的更正之外,我重新命名了一些项目以使其作用更加明确。

如果有标准解,和我的不一样,我想看看

这是我更正的程序:

with Ada.Text_IO;
with Ada.Calendar;

package body Tasking is
   
   t_step:  constant Duration:= 0.1;
      
   protected Sync is
      procedure Enable_App;         -- enable application
      procedure Request_Disable;    -- request disabling of app.
      procedure Disable_If_Req;     -- disable app. if requested
      function Enabled return Boolean;  -- application is enabled
      procedure Request_Quit;           -- task shall terminate
      function Quit_Requested return Boolean;   -- task termination requested
      procedure Set_Time (t: Ada.Calendar.Time);    -- set execution time
      function Get_Time return Ada.Calendar.Time;   -- get execution time
   private
      running:  Boolean:= false;        -- application is enabled
      stopreq:  Boolean:= false;        -- disabling has been requested
      quitreq:  Boolean:= false;        -- task termination requested
      ti:   Ada.Calendar.Time;      -- time of next app. execution
   end Sync;
   
   protected body Sync is
      
      procedure Enable_App is begin running:= true; end Enable_App;
      
      procedure Request_Disable is
      begin
     if running then stopreq:= true; end if;
      end Request_Disable;
      
      procedure Disable_If_Req is
      begin
     if stopreq then
        running:= false;
        stopreq:= false;
     end if;
      end Disable_If_Req;
      
      function Enabled return Boolean is
      begin return running; end Enabled;
      
      procedure Request_Quit is
      begin quitreq:= true; end Request_Quit;
      
      function Quit_Requested return Boolean
      is begin return quitreq; end Quit_Requested;
      
      procedure Set_Time (t: Ada.Calendar.Time) is
      begin ti:= t; end Set_Time;
      
      function Get_Time return Ada.Calendar.Time is
     begin return ti; end Get_Time;
      
   end Sync;
   
   
   task body Thread is
      use type Ada.Calendar.Time;
      now:  Ada.Calendar.Time;
   begin
      Ada.Text_IO.Put_Line("starting task");
      while not Sync.Quit_Requested loop
     if sync.Enabled then
        -- increment time if it is too late
        now:= Ada.Calendar.Clock;
        while sync.Get_Time <= now loop
           sync.Set_Time (sync.Get_Time + t_step);
        end loop;
        -- wait until next execution time
        delay until sync.Get_Time;
        -- execute application and set time for next execution
        Ada.Text_IO.Put(".");
        sync.Set_Time (sync.Get_Time + t_step);
        -- disable application if this has been requested
        sync.Disable_If_Req;
     else
        -- wait for enabling and set time for next execution
        delay t_step;
        sync.Set_Time (Ada.Calendar.Clock + t_Step);
     end if;
       end loop;
   end Thread;
   
   
   procedure Start is
   begin
      Sync.Enable_App;
   end Start;
   
   function Is_Running return Boolean is
   begin
      return Sync.Enabled;
   end Is_Running;
   
   procedure Request_Stop is
   begin
      Ada.Text_IO.Put_Line("");
      Sync.Request_Disable;
   end Request_Stop;
   
   procedure Request_Quit is
   begin
      Sync.Request_Quit;
   end Request_Quit;
   
end Tasking;

关于 Ada 任务的一些一般想法:

  • 从概念上讲,Ada 任务有自己的处理器,没有其他任务在其上运行。调度,例如确定任务下一步应该做什么,通常是任务的责任。
  • 当任务必须等待一个事件时,任务通常最好是阻塞而不是轮询。
  • Ada.Calendar 通常是当地时间,甚至可以倒退。如果可能,任务应使用 Ada.Real_Time 自行安排。

所以我会做类似的事情

protected Control is
   procedure Set_Running (Running : in Boolean := True);
   -- Set whether the task should run or not
   -- Initially Running is False

   entry Wait_Until_Running;
   -- Blocks the caller until Running is set to True

   function Running return Boolean;
   -- Returns the current value of Running

   procedure Quit_Now;
   -- Tell the task to quit

   function Quitting return Boolean;
   -- Returns True when Quit_Now has been called; False otherwise
private -- Control
   Run  : Boolean := False;
   Quit : Boolean := False;
end Control;

task body T is
   Step : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Milliseconds (100);

   Next : Ada.Real_Time.Time;
begin -- T
   Forever : loop
      Control.Wait_Until_Running;
      Next := Ada.Real_Time.Clock;

      Run : while Control.Running loop
         exit Forever when Control.Quitting;

         delay until Next;

         Next := Next + Step;
         -- The task does its thing here
         Ada.Text_IO.Put (Item => 'a');
      end loop Run;
   end loop Forever;
end T;

protected body Control is
   procedure Set_Running (Running : in Boolean := True) is
   begin
      Run := Running;
   end Set_Running;

   entry Wait_Until_Running when Run is
   begin
      null;
   end Wait_Until_Running;

   function Running return Boolean is (Run);

   procedure Quit_Now is
   begin
      Run := True; -- Release the task if it is blocked on Wait_Until_Running
      Quit := True;
   end Quit_Now;

   function Quitting return Boolean is (Quit);
end Control;

感谢您对目标的新描述,@hreba,很高兴听到您的程序现在按预期工作。

我同意@Anh Vo 的观点,应用程序任务可以通过任务条目来控制,但我认为使用受保护对象 (PO) 更可取,因为它将调用者(主任务)与应用程序任务分离-- 它不会让调用者等到应用程序任务准备好接受入口调用。但是,我会减少 PO 来处理 inter-task 通信。在@hreba 代码中,我认为没有理由使用受保护的操作 Set_Time 和 Get_Time,因为时间似乎完全由应用程序任务控制。

下面是我的编程方式。首先,我会为应用程序任务的状态定义一个枚举,而不是一些布尔值:

type App_State is (Enabled, Disabled, Stopping, Stopped);

在@hreba 代码中,应用程序任务似乎最初“不是运行”,所以

Initial_State : constant App_State := Disabled;

然后 PO 将声明为(我省略了评论,因为我将以散文形式评论):

protected App_Control is
   procedure Enable;
   procedure Disable;
   procedure Stop;
   entry Get_New_State (New_State : out App_State);
   entry Wait_Until_Stopped;
private
    State : App_State := Initial_State;
    Old_State : App_State := Initial_State;
end App_Control;

PO 操作启用、禁用和停止做显而易见的事情:

procedure Enable
is
begin
   State := Enabled;
end Enable;

禁用和停止类似。停止将状态设置为正在停止。

条目Get_New_State将在应用程序任务中调用以接收新状态。它是一个条目,而不是函数或过程,因此只有在状态发生变化时才能调用它:

entry Get_New_State (New_State : out App_State)
when State /= Old_State
is
begin
   New_State := State;
   Old_State := State;
   if State = Stopping then
      State := Stopped;
   end if;
end Get_New_State;

在这里,一旦应用程序任务被赋予,我就自动从 Stopping 过渡到 Stopped New_State = Stopping。如果应用程序任务在被认为真正停止之前必须执行一些 clean-up,则应将该转换移动到单独的操作,例如过程 App_Control.Has_Stopped,然后从应用程序任务调用 clean-up.

条目Wait_Until_Stopped可以在主任务中调用,等待应用任务停止。很简单:

entry Wait_Until_Stopped
when State = Stopped
is
begin
   null;
end Wait_Until_Stopped;

现在是申请任务。我的设计与原来的设计有一个主要区别:为了使任务对“停止”命令的响应更快,我使用 Get_New_State 的定时进入调用让该调用在应用程序任务等待下一个时发生激活时间。如果任务是每 0.1 秒 运行,这可能无关紧要,但在某些其他情况下,任务可能是每 10 秒或每 10 分钟 运行,然后它可能很重要。所以这是任务正文:

task body App_Thread
is
   use Ada.Calendar;
   Period : constant Duration := 0.1;
   State : App_State := Initial_State;
   Next_Time : Time := Clock + Period;
begin
   loop
      select
         App_Control.Get_New_State (New_State => State);
         exit when State = Stopping;
      or
         delay until Next_Time;
         if State = Enabled then
            Execute_The_Application;
         end if;
         Next_Time := Next_Time + Period;
      end select;
   end loop;
end App_Thread;

要解耦调用任务和被调用任务,不要使用accept {entry name} do 语句。只需将其分成两个单独的语句,如下所示,条目 Start_Work 例如

--耦合案例 接受 Start_Work 做 Execute_The_Application 结束 Start_Work;

--解耦案例 接受 Start_Work; Execute_The_Application;

这是我对@hreba 要求的实现。

包任务是 -- 更多代码

task Thread2 is
    entry Start_Work;
    entry Stop_Work;
    entry Quitting_Time;
end Thread2;

task body Thread2 is
    use type Ada.Real_Time.Time;
    T_Step : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Milliseconds (100);
    Next_Time : Ada.Real_Time.Time := Ada.Real_Time.Clock;
begin
    Ada.Text_IO.Put_Line("task Thread2 elaborates and activates");

    Forever: loop
        select
            accept Quitting_Time;
                Ada.Text_IO.Put_Line ("task Thread2 terminates. Later alligator");
                exit Forever;  -- later alligator
        or
            accept Start_Work;
            Working: loop
                Ada.Text_IO.Put_Line ("Ada");
                Next_Time := Next_Time + T_Step;
                select
                    accept Stop_Work;
                    exit Working;
                or
                    delay until Next_Time;
                end select;
            end loop Working;
        or
            Terminate;
        end select;
    end loop Forever;   
end Thread2;

-- 更多代码 结束任务;