Моя фактическая программа создает задачу, на которую я не реагирую на управляющие сообщения должным образом. Поскольку он стал довольно большим, я представляю короткую тестовую программу с той же логикой управления. Он создает фоновую задачу, которая повторяет цикл каждые 0,1 с. В зависимости от «запущенного» защищенного флага он печатает «a» или ничего не делает. Когда я устанавливаю «работает», программа сразу же запускается, печатая буквы «а». Но когда я устанавливаю «stopreq», до остановки требуется секунд, иногда более 10. Я бы ожидал время отклика 0,1 или 0,2 с.
У кого-нибудь есть объяснение и решение?
Моя основная программа открывает окно с 3 кнопками: «Старт», которая вызывает подпрограмму «Пуск», расположенную ниже, «Стоп», которая вызывает Request_Stop, и «Выход», вызывающую Request_Quit. Я работаю на ПК под управлением Linux.
А вот и тело моего пакета Tasking. Если вам нужно больше, просто скажите мне, и я опубликую другие части.
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) некорректно — это «ограниченная ошибка». Если это работает, он, вероятно, блокирует все другие вызовы этого защищенного объекта до тех пор, пока не истечет задержка. Я предлагаю вам начать с этого, когда вы попытаетесь исправить код.
Просто добавьте к комментарию Hiklas, что защищенный объект должен использоваться для защиты общих данных, а не для управления задачами. Для управления задачей используйте запись задачи, как показано ниже, например. задача Thread является записью Start; вход Выйти; конец нити; По умолчанию задача бездействует, когда не выполняется.
Пока не ясно, как решить мою проблему (см. мой пост ниже) с помощью записей: выполнять что-то через равные промежутки времени и включать/отключать это асинхронно. Приветствуются ответы на мой вопрос о стандартном решении.
@hreba Взгляните на раздел 9 Справочного руководства по Аде (ARM), adaic.org/resources/add_content/standards/12rm/html/RM-9.html. Я думаю, что ваше требование может быть реализовано с использованием функций задач без необходимости защищенного объекта.
Я попытался опубликовать свою реализацию требования @hreba, используя только задачи. Но мои коды были отклонены из-за какого-то непринятого формата. Если вы не заинтересованы в этом, дайте мне знать, я найду другой способ опубликовать это.
Благодаря вашим комментариям программа теперь работает корректно.
Джесперу Куорнингу:
«Время в прошлом» здесь не было проблемой, но могло быть так в моем реальном программном проекте. Тем не менее, я включил лекарство в исправленную версию ниже, чтобы оно могло служить образцом для других. Спасибо за подсказку.
Никласу Холсти:
Я переместил «отложить до» из защищенной операции, и теперь программа работает, как и ожидалось, спасибо за объяснение. (Иногда программа, которая работает плохо, хуже, чем программа, которая вообще не работает, потому что в последнем случае вы более серьезно относитесь к предупреждениям компилятора.)
Что я хочу сделать, так это:
Моя идея реализации была:
Если есть стандартное решение, отличное от моего, хотелось бы его увидеть.
Вот моя исправленная программа:
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;
Некоторые общие идеи о постановке задач в Аде:
Поэтому я бы сделал что-то вроде
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 Enable, Disable и Stop делают очевидные вещи:
procedure Enable
is
begin
State := Enabled;
end Enable;
и аналогично для Disable и Stop. 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;
Здесь я сделал автоматический переход от Stopping к Stopped, как только задаче приложения было присвоено New_State = Stopping. Если прикладная задача должна выполнить некоторую очистку, прежде чем ее можно будет считать действительно остановленной, этот переход следует перенести в отдельную операцию, например процедуру App_Control.Has_Stopped, которая будет вызываться из прикладной задачи после этой очистки.
Запись 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}. Просто разбейте это на два отдельных оператора, как показано ниже, например, с записью Start_Work.
--корпус муфты принять Start_Work сделать Execute_The_Application конец Start_Work;
-- разъединяющий корпус принять Start_Work; Execute_The_Application;
Это только отделяет /end/ рандеву Start_Work от Execute_The_Application, так что вызов Start_Work возвращается, и вызывающая сторона может продолжать работу, пока выполняется Execute_The_Application (это то, что @hreba хочет, чтобы быть уверенным). Это не меняет того факта, что если вызывающая задача пытается снова вызвать какую-либо запись, например запись Stop, она должна ждать, пока вызываемая задача не выполнит принятие для этой записи, поэтому, возможно, ей придется ждать всего Execute_The_Application. , что может привести к тому, что графический интерфейс будет очень невосприимчивым.
Вот моя реализация требования @hreba.
пакет Tasking есть -- больше кодов
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;
-- больше кодов конец задачи;
Не уверен в альтернативе Terminate, ни в семантике, ни в цели. Когда это называется? Что он должен делать?
Просто замените вызовы Sync.Start на Thread2.Start_Work, Sync.Request_Stop на Thread2.Stop_Work и Sync.Request_Quit на Thread2.Quitting_Time. Альтернатива Select Terminate позволяет завершить задачу Threads при завершении программы, если не вызывается Quitting_Time. Кроме того, результат должен быть одинаковым. Кстати, я использовал Ada.Text_IO.Put_Line("Ada"); вместо Ada.Text_IO.Put("a"); для легкого отслеживания вывода.
Может быть бесконечный цикл с
delay until
временем в прошлом..