Я размышлял над этим в течение некоторого времени и все больше и больше узнаю о потоках, исполнителях и т. Д. По мере того, как я иду. У меня есть приблизительное представление об исполнителях и потоках, но я чувствую себя немного застрявшим.
Вот что я пытаюсь сделать.
Есть команды, а есть действия. Команда имеет имя и может вызываться пользователем произвольно, например !playsong, !cheer и т. д. Действие — это то, что отправляет работу службе; например, запрос клиента веб-сокета на отправку нового сообщения или запрос клиента IRC на отправку нового сообщения и т. д.
Когда команда выполняется, она выполняет свои Действия по порядку одно за другим.
Например, команда !cheer может иметь четыре действия:
- Сделайте запрос через веб-сокет и дождитесь успешного ответа (например, покажите элемент сцены в OBS)
- Отправить IRC-сообщение (например, отправить сообщение в чат). После отправки тогда,
- Подождите 1-3 секунды (например, ожидание окончания воспроизведения видео). Как только ожидание закончилось, затем
- Сделайте еще один запрос веб-сокета (например, скройте элемент сцены из шага 1)
Мало того, что они должны выполняться по порядку, НО мы не можем запустить их все одновременно (Действия 1, 2 и 4 завершаются первыми, а затем Действие 3 завершается последним); каждое Действие зависит от того, было ли его предыдущее завершено первым.
Вдобавок ко всему, Команды могут быть отправлены клиентами произвольно в любое время и не должны блокировать друг друга. Например, !longcommand может быть запущен, но не заблокирует запуск !shortcommand (при условии, что базовые службы не заблокированы).
Вот что я думаю сделать:
Я знаю, что могу использовать Future/Callable для блокировки в ожидании результата выполнения в данном потоке, поэтому каждое действие должно возвращать будущее при запуске (будущее исходит из соответствующей службы, которую он использует). Затем я могу просто вызвать действия одно за другим блокирующим образом, как это, для команды, чтобы убедиться, что они выполняются по порядку, и каждый ожидает завершения другого:
class ExecutableCommand implments Runnable {
// omitted for brevity
run() {
for(Action action:command.getActions()) {
action.run().get();
}
}
Но как мне справиться с выполнением команд? Думаю, я бы отправил каждую команду через исполнителя, может быть, ThreadPoolExecutor, подобный этому, при каждой отправке?
class ExecutorServiceWrapper {
private final ExecutorService executorService = Executors.newThreadPoolExecutor(4);
void submit(ExecutableCommand command) {
executorService.submit(command)
}
}
И тогда каждый клиент ofc просто сохранит ссылку на ExecutorServiceWrapper и вызовет его в ответ на события, которые их запускают:
class FromChatHandler() {
private final ExecutorServiceWrapper masterQueue;
onMessage(String message) {
Command command = // parse what command to lookup from message
masterQueue.submit(command)
}
}
@RestController // or whatever
class MyController() {
private final ExecutorServiceWrapper masterQueue;
@Post
executeCommandByName(String commandName) {
Command command = // lookup command
masterQueue.submit(command)
}
}
class directHandler() {
private final ExecutorServiceWrapper masterQueue;
handle(Command command) {
Command command = // build the command given the message
masterQueue.submit(command)
}
}
Я предполагаю, что, поскольку каждая команда отправляется исполнителю, каждая из них отправляется в свой собственный поток, поэтому он не будет блокировать другие.
Но я не уверен, должен ли я делать то, что я делаю выше, с ExecutableCommand и выполнять каждое действие внутри команды, как я.
Кроме того, я не уверен, справится ли он с этим случаем: пул потоков фиксирован на 5 потоков. Выполнено 5 команд. Они давно работают и используют разные сервисы, но базовые сервисы не блокируются и все еще могут принимать работу. Кто-то пытается выполнить 6-ю команду — его нельзя блокировать, потому что базовые службы все еще могут принимать работу.
Есть ли лучший способ сделать это? Я на правильном пути?