Задание: Очень медленный ответ

Моя фактическая программа создает задачу, на которую я не реагирую на управляющие сообщения должным образом. Поскольку он стал довольно большим, я представляю короткую тестовую программу с той же логикой управления. Он создает фоновую задачу, которая повторяет цикл каждые 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;

Может быть бесконечный цикл с delay until временем в прошлом..

Jesper Quorning 24.03.2022 17:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
154
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Ваш код слишком плохо описан и прокомментирован, чтобы я мог понять, что вы от него хотите.

Но использование оператора «задержка до» в защищенной операции (Sync.Delay_Until) некорректно — это «ограниченная ошибка». Если это работает, он, вероятно, блокирует все другие вызовы этого защищенного объекта до тех пор, пока не истечет задержка. Я предлагаю вам начать с этого, когда вы попытаетесь исправить код.

Просто добавьте к комментарию Hiklas, что защищенный объект должен использоваться для защиты общих данных, а не для управления задачами. Для управления задачей используйте запись задачи, как показано ниже, например. задача Thread является записью Start; вход Выйти; конец нити; По умолчанию задача бездействует, когда не выполняется.

Anh Vo 24.03.2022 18:29

Пока не ясно, как решить мою проблему (см. мой пост ниже) с помощью записей: выполнять что-то через равные промежутки времени и включать/отключать это асинхронно. Приветствуются ответы на мой вопрос о стандартном решении.

hreba 25.03.2022 12:20

@hreba Взгляните на раздел 9 Справочного руководства по Аде (ARM), adaic.org/resources/add_content/standards/12rm/html/RM-9.htm‌​l. Я думаю, что ваше требование может быть реализовано с использованием функций задач без необходимости защищенного объекта.

Anh Vo 25.03.2022 19:47

Я попытался опубликовать свою реализацию требования @hreba, используя только задачи. Но мои коды были отклонены из-за какого-то непринятого формата. Если вы не заинтересованы в этом, дайте мне знать, я найду другой способ опубликовать это.

Anh Vo 26.03.2022 21:56

Благодаря вашим комментариям программа теперь работает корректно.

Джесперу Куорнингу:

«Время в прошлом» здесь не было проблемой, но могло быть так в моем реальном программном проекте. Тем не менее, я включил лекарство в исправленную версию ниже, чтобы оно могло служить образцом для других. Спасибо за подсказку.

Никласу Холсти:

Я переместил «отложить до» из защищенной операции, и теперь программа работает, как и ожидалось, спасибо за объяснение. (Иногда программа, которая работает плохо, хуже, чем программа, которая вообще не работает, потому что в последнем случае вы более серьезно относитесь к предупреждениям компилятора.)

Что я хочу сделать, так это:

  • Выполнение приложения через точно определенные промежутки времени
  • Возможность включить/отключить его в любое время
  • Дайте ему время остановиться только тогда, когда он находится в четко определенном состоянии
  • Зная, когда это действительно остановилось
  • Контролируемое прекращение всего этого.

Моя идея реализации была:

  • Вызывать приложение в бесконечном цикле как отдельную задачу
  • Проверьте, был ли запрошен выход в начале цикла
  • Оператор if, который запускает приложение, если оно включено
  • Простая задержка в противном случае. Управление из других пакетов осуществляется с помощью общедоступных подпрограмм. Start, 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.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 27.03.2022 13:26

Спасибо за ваше новое описание целей, @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;

Большое спасибо за программу и подробные пояснения. Для меня это отличный пример для изучения барьера и выбора понятий, о которых я не знал.

hreba 27.03.2022 13:29

Чтобы отделить вызов задачи от вызываемой задачи, не используйте оператор 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. , что может привести к тому, что графический интерфейс будет очень невосприимчивым.

Niklas Holsti 28.03.2022 11:16

Вот моя реализация требования @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, ни в семантике, ни в цели. Когда это называется? Что он должен делать?

hreba 28.03.2022 22:12

Просто замените вызовы 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"); для легкого отслеживания вывода.

Anh Vo 29.03.2022 03:56

Другие вопросы по теме