Шаблон / архитектура для выбора и выполнения задач в зависимости от входящей команды - PullRequest
0 голосов
/ 07 мая 2018

Обычно я решаю это с помощью чего-то вроде:

task = _taskProvider.getNextFromQueue();

if (task.id == TaskExecutor.TasksEnum.One) {
    _taskExecutor.executeTaskOne();
    return;
} else if (task.id == TaskExecutor.TasksEnum.Two) {
    _taskExecutor.executeTaskTwo();
    return;
} else if (task.id == TaskExecutor.TasksEnum.Three) {
    _taskExecutor.executeTaskThree();
    return;
}
//and so on...

Я мог бы переключить if-else на switch при необходимости. Я верю, что существует лучший способ реализовать подобный код. Одна вещь, которая приходит на ум, - это использовать таблицу (карту) для хранения идентификаторов задач и указателей на соответствующие функции, но я не уверен, что это полезно и обеспечивает адекватную производительность.

Еще одна вещь, которая меня беспокоит, это механизм уведомлений. Эта проблема возникает, когда TaskProvider и TaskExecutor являются двумя отдельными объектами, которые работают в разных потоках. Становится еще хуже, если нескольким действиям требуется некоторое время, чтобы завершить выполнение, и мне нужно создать логику, чтобы вызывающая сторона ожидала результата.

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

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

Обновление 1:

Еще немного информации об общей архитектуре и проблемах Есть три объекта:

  • Hardware - управляет аппаратными сигналами (входы, выходы). Заботится только об оборудовании, не задает вопросов о том, почему?
  • Network - управляет удаленными подключениями к устройству. Заботится только о передаче данных. Опять же, ему не важно, что представляют собой данные.
  • Controller - контролирует реальную задачу / алгоритм устройства (что делает это устройство). Это мозг устройства.

Каждая сущность работает в своем собственном потоке. Иногда происходит событие, и один объект должен отправить это событие другому объекту (или всем им). Например, команда с network включает какой-либо светодиод, управляемый аппаратным обеспечением, или об ошибке включения этого индикатора следует сигнализировать как network (уведомить удаленных клиентов), так и controller (выполнить аварийную остановку).

Прямо сейчас у каждой сущности (класса) есть набор функций (методов), которые другие сущности вызывают, когда они этой сущностью что-то делают. Если вызываемой функции требуется время для выполнения, объект вызывающей стороны блокируется, что плохо. Создание фонового потока для обработки каждой команды (или события) также кажется неправильным. Прямо сейчас каждый объект имеет свою очередь команд и выполняет свои команды последовательно (execute(queue.getNext())), но это неэффективно, потому что некоторые команды выполняются быстро, а другие требуют времени и ресурсов.

Ответы [ 2 ]

0 голосов
/ 11 мая 2018

Можете ли вы иметь Task в качестве базового класса и позволить `getNextFromQueue () возвращать конкретный класс, производный от него? Вот простой код на лету, чтобы дать идею /

class Task
{
public:
void execute() = 0;

}

class TaskOne : public Task
{
public:
void execute()
  {
     // execute the task here
  }
}

Итак, ваш код будет выглядеть так:

Task * task = _taskProvider.getNextFromQueue();

if (task)
   task->execute()
0 голосов
/ 07 мая 2018

Я верю, что существует лучший способ реализовать подобный код.

Оператор switch настолько хорош, насколько вы можете его получить. Он (обычно) генерирует таблицу переходов в вашем машинном коде, которая будет напрямую отправлять программный счетчик в местоположение для продолжения выполнения.

Использование map (красно-чёрное дерево) для хранения ids и pointers to functions не так эффективно, как совершенное хеширование, как в switch, и может помешать встроенной оптимизации.

Я бы выполнил его в фоновом потоке, что означает, что executeTaskTwo () вернется почти сразу.

Я не очень разбираюсь в многопоточности, но с чего бы вам возвращаться раньше?

Не указывает ли поток на join остальные потоки после его завершения?

Если вы имеете в виду, что основной поток продолжает делать другие вещи, то у вас может быть std::atomic<bool>, который сообщает вам, завершен ли процесс. Если в него записывается только один поток (рабочий поток), а другие читают (основной поток), поведение четко определено.

Если вы не хотите хранить кучу логических значений и просто хотите узнать, запущен ли еще процесс, у вас может быть счетчик , который вы увеличиваете и уменьшение по мере начала и окончания потоков.

...