我的实际程序创建了一个任务,但我没有对控制消息做出应有的反应。随着它变得越来越大,我提出了一个具有相同控制逻辑的简短测试程序。它创建一个每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;
--更多代码结束任务;