任务:反应非常慢



我的实际程序创建了一个任务,但我没有对控制消息做出应有的反应。随着它变得越来越大,我提出了一个具有相同控制逻辑的简短测试程序。它创建一个每0.1s重复一个循环的后台任务;运行";它打印出一个"a",或者什么也不做。当我设置";"运行";,程序立即关闭,打印‘a’。但当我设定";stopreq";,它需要几秒钟,有时甚至超过10秒,直到停止。我预计响应时间为0.1或0.2秒。

有人有解释和解决方案吗?

我的主程序打开一个有3个按钮的窗口:;"开始";,它调用下面的子程序Start;停止";,其调用Request_ Stop;退出";调用Request_Quit。我在一台运行Linux的电脑上工作。

这是我的任务包的正文。如果你需要更多,只要告诉我,我会发布其他部分。

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)不正确——它是一个"错误";有界误差";。如果它有效,它可能会阻止对该受保护对象的所有其他调用,直到延迟到期。我建议您在尝试更正代码时应该从那里开始。

感谢您的评论,该程序现在工作正常。

致Jesper报价:

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

致Niklas Holsti:

我把";延迟到";退出受保护的操作,程序现在按预期工作,感谢您的解释。(有时,一个工作不好的程序比一个根本不工作的程序更糟糕,因为在后一种情况下,你会更认真地对待编译器警告。)

我想做的是:

  • 以精确定义的时间间隔执行应用程序
  • 能够随时启用/禁用
  • 只有在定义良好的状态下才有时间停止
  • 知道,当它真的停止了
  • 有控制地终止整个事件

我的想法是:

  • 在无休止的循环中调用应用程序作为单独的任务
  • 检查循环顶部是否已请求退出
  • 如果启用,则执行应用程序的if语句
  • 否则就是简单的延迟。来自其他包的控制是用公共子程序完成的启动、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.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来处理任务间的通信。在@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;

以及类似地用于禁用和停止。Stop将State设置为Stopping。

将在应用程序任务中调用条目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;

在这里,一旦应用程序任务被赋予New_State=Stopping,我就自动从Stopping转换为Stopped。如果应用程序任务在被视为真正停止之前必须进行一些清理,则应将该转换移动到一个单独的操作,例如过程App_Control.has_Stop,以便在清理之后从应用程序任务调用。

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

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;执行应用程序;

以下是我对@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;

--更多代码结束任务;

相关内容

最新更新