С#, как добиться последовательного поведения блокировки в многопоточном приложении

Я пишу приложение, которое должно имитировать поведение ПЛК. Это означает, что мне нужно запустить несколько потоков, чтобы убедиться, что активен только один поток, а все остальные приостановлены. Например:

  • поток 1 повторяется каждые 130 мс и блокирует все остальные потоки. Эффективное время выполнения составляет 30 мс, а дополнительные 100 мс перед перезапуском потока могут использоваться другими потоками.
  • поток 2 повторяется каждые 300 мс и блокирует все потоки, кроме потока 1. Эффективное время выполнения составляет 50 мс (оставшиеся 250 мс могут использоваться другими потоками). Поток 2 приостанавливается до тех пор, пока поток 1 не завершит выполнение кода (оставшиеся 100 мс потока 1), и как только поток 1 спит, он возобновляется с того места, где он был приостановлен.
  • поток 3 повторяется каждые 1000 мс. Эффективное время выполнения составляет 100 мс. Этот поток продолжает выполнение, только если все остальные потоки приостановлены.

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

Я прочитал несколько сообщений и узнал, что Thread.suspend не рекомендуется, а операции с семафором или монитором означают, что код выполняется до определенной и фиксированной точки в коде, в то время как я должен приостанавливать потоки именно там, где выполнение прибыло, когда другой поток (с более высоким "приоритетом") называется.

Я также просмотрел настройку приоритета, но она не кажется актуальной на 100%, поскольку система может переопределять приоритеты.

Есть ли правильный или хотя бы надежный способ кодирования механизма блокировки?


person Maximilian Eheim    schedule 23.06.2015    source источник
comment
Вероятно, вам нужно приложение реального времени, в котором поток с высоким приоритетом немедленно и безоговорочно вытесняет поток с низким приоритетом, а время имеет решающее значение. Сама Windows не подходит для приложений жесткого реального времени. Является ли ваше приложение критически важным для производительности? Если это не так, вы можете имитировать немедленное преимущество, вставив условное ожидание в потоки с низким приоритетом.   -  person Tsyvarev    schedule 23.06.2015
comment
Было бы неплохо работать в режиме реального времени, но ни Windows, ни C# не подходят для этого. Требуемая производительность легко достижима, я просто застрял в механизме приостановки. Я должен убедиться, что задачи с более высоким приоритетом блокируют задачу с более низким приоритетом, где бы они ни находились в последовательности кода, а разблокировка задачи должна продолжать выполнение с того места, где оно было приостановлено.   -  person Maximilian Eheim    schedule 23.06.2015
comment
Поскольку производительность не является проблемой, вы можете эмулировать приоритет в реальном времени и немедленное преимущество, вставив что-то вроде SleepIfNeeded(); call в код вашего потока. Этот вызов должен ожидать в случае выполнения потока с более высоким приоритетом. Нет необходимости вставлять этот вызов после каждой строки кода, например, вы можете использовать его только перед модификацией общедоступных (наблюдаемых) данных. Будьте осторожны, чтобы использовать этот вызов внутри критических секций, так как это может привести к взаимоблокировке (из-за инверсии приоритета).   -  person Tsyvarev    schedule 23.06.2015
comment
В этом случае код работает до определенного момента. Это решает мои проблемы с обновлениями переменных, но это означает, что несколько потоков будут выполняться одновременно. Мое приложение чувствительно ко времени, а это означает, что мне нужно выполнять код как последовательность, иначе может случиться так, что поток не завершится в заранее определенное время выполнения из-за взаимодействия с другими потоками, но при одновременном запуске код уже разработан и поэтому я никогда не узнаю.   -  person Maximilian Eheim    schedule 23.06.2015
comment
Я отредактировал вопрос, чтобы более конкретно указать аспект, чувствительный ко времени.   -  person Maximilian Eheim    schedule 23.06.2015
comment
Я не думаю, что вам нужно несколько потоков вообще. ПЛК выполняет простой последовательный цикл. Чтение входных данных, решение лестницы (последовательно), запись выходных данных, повторение. Это кажется слишком сложным...   -  person J...    schedule 17.07.2015
comment
ПЛК выполняет несколько параллельных задач (OB), которые могут быть разными программами или просто функциями одной программы с меньшим приоритетом (например, графика или связь). Проблема в том, что ПЛК на самом деле не являются многозадачными, а выполняют задачи, основанные на времени или вызываемые прерыванием, и эти условия затрудняют моделирование ПЛК на ПК. Время, необходимое для выполнения одного блока программы, может сильно изменить вывод, если в это время задача прерывается неправильным образом.   -  person Maximilian Eheim    schedule 18.07.2015
comment
(например: задача 1 имеет контроль тайм-аута для определенной переменной, которой управляет задача 2. Если потоки выполняются неправильно, моделирование приведет к срабатыванию тайм-аута, в то время как реальный ПЛК этого не сделает).   -  person Maximilian Eheim    schedule 18.07.2015


Ответы (2)


Я не думаю, что вам вообще нужно обременять себя Thread. Вместо этого вы можете использовать Tasks с приоритетным TaskScheduler (это не так уж сложно написать или найти в гугле).

Это упрощает написание кода, например, поток с наивысшим приоритетом может выглядеть примерно так:

while (!cancellationRequested)
{
  var repeatTask = Task.Delay(130);

  // Do your high priority work      

  await repeatTask;
}

Другие ваши задачи будут иметь аналогичную базовую структуру, но им будет присвоен более низкий приоритет в планировщике задач (обычно этим занимается планировщик задач, имеющий отдельную очередь для каждого из приоритетов задач). Время от времени они могут проверить, есть ли задача с более высоким приоритетом, и если да, то они могут выполнить await Task.Yield();. Фактически, в вашем случае кажется, что вам даже не нужны настоящие очереди - это делает это намного проще и даже лучше, позволяет вам действительно эффективно использовать Task.Yield.

Конечным результатом является то, что все три ваши периодические задачи эффективно выполняются только в одном потоке (или даже вообще без потока, если все они ожидают).

Конечно, это зависит от совместной многозадачности. На самом деле невозможно справиться с полномасштабной операцией в реальном времени, такой как вытеснение в Windows, а решения с частичным вытеснением, как правило, полны проблем. Если вы контролируете большую часть времени, затрачиваемого на задачу (и разгружаете любую другую работу, используя асинхронный ввод-вывод), совместное решение на самом деле гораздо более эффективно и может дать вам гораздо меньше задержек (хотя оно действительно должно не имеет большого значения).

person Luaan    schedule 23.06.2015
comment
Это интересно. Я попробую. Спасибо - person Maximilian Eheim; 23.06.2015
comment
Я попробовал простой пример и заметил, что, как и другие методы, выполнение задачи не приостанавливается в том положении, в котором она когда-либо находилась, когда вызывается задача с более высоким приоритетом, но завершается до тех пор, пока не будет выполнена функция ожидания. - person Maximilian Eheim; 26.06.2015
comment
@MaximilianEheim Да, это по-прежнему совместная многозадачность. На самом деле нет никакого способа упредить. Вы можете асинхронно вызвать прерывание или приостановку потока, но это плохая идея — код, который вы пытаетесь приостановить, должен иметь право голоса, когда это происходит. Суть в том, что если вы разумно используете await во всем коде, это все равно должно давать вам лучшую задержку, чем упреждающая многозадачность - Windows не среда для написания приложений реального времени (и никакая , Unix тоже нет). Ваш обратный вызов никогда не будет вызываться ровно каждые 100 мс. - person Luaan; 27.06.2015

Надеюсь, я правильно понял ваш вопрос :)

Одной из возможностей вашей проблемы может быть использование параллельной очереди: http://https://msdn.microsoft.com/de-de/library/dd267265(v=vs.110).aspx

Например, вы создаете перечисление для управления своим состоянием и запускаете очередь:

 private ConcurrentQueue<Action> _clientActions ;
     private enum Statuskatalog
      {
           Idle,
           Busy
       };

Создайте таймер для запуска и создайте функцию таймера.

Timer _taskTimer = new Timer(ProcessPendingTasks, null, 100, 333);



private void ProcessPendingTasks(object x)
         {
           _status = Statuskatalog.Busy;
             _taskTimer.Change(Timeout.Infinite, Timeout.Infinite);
             Action currentTask;
             while( _clientActions.TryDequeue( out currentTask ))
             {
                 var task = new Task(currentTask);
                 task.Start();         
                 task.Wait();
             }

         _status=Statuskatalog.Idle;
         }

Теперь вам нужно только добавить свои задачи в качестве делегатов в очередь:

_clientActions.Enqueue(delegate { **Your task** });
            if (_status == Statuskatalog.Idle) _taskTimer.Change(0, 333);

На этой основе вы можете управлять своими особыми требованиями, которые вы просили.

Надеюсь, это было то, что вы искали.

person Leonard Klausmann    schedule 23.06.2015
comment
Кажется, хорошее начало, но если я не ошибаюсь, очередь — это система FIFO, поэтому она просто добавляет задачи одну за другой, но не блокирует поток с более низким приоритетом при вызове потока с более высоким приоритетом, верно? - person Maximilian Eheim; 23.06.2015
comment
Я думаю, что вы можете управлять этим с помощью этой системы, добавляя некоторые логические вопросы. Альтернативой может быть использование шаблона приоритетной очереди msdn.microsoft.com/de-de /библиотека/dn589794.aspx - person Leonard Klausmann; 23.06.2015
comment
Насколько я понимаю: оба метода не могут заблокировать поток после его запуска, верно? - person Maximilian Eheim; 23.06.2015
comment
Да, это правда: они только помогают вам упорядочивать темы в правильном порядке. Чтобы приостановить поток, вы можете использовать простой механизм, например прервать поток и перевести его в режим сна: msdn.microsoft.com/de-de/library/tttdef8x%28v=vs.110%29.aspx Но это две разные проблемы, с которыми вы сталкиваетесь :) - person Leonard Klausmann; 23.06.2015