Перенаправить вызов метода на что-то с файловым дескриптором — asyncore

У нас есть сетевой клиент на основе asyncore с сетевым подключением пользователя, воплощенным в файле Dispatcher. Цель состоит в том, чтобы пользователь, работающий с интерактивного терминала, мог вводить команды сетевого запроса, которые отправлялись бы на сервер и в конечном итоге возвращались с ответом. Клиент написан как асинхронный, поэтому пользователь может запускать несколько запросов на разных серверах одновременно, собирая результаты по мере их появления.

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

Одно из плохих решений выглядит следующим образом. Мы запускаем цикл выбора в отдельном потоке и заставляем пользователя вводить входные данные в потокобезопасную очередь записи, вызывая метод, который мы определили в нашем Dispatcher. Что-то типа

def myConnection.enqueue(self, someData):
    self.lock.acquire()
    self.queue.put(someData)
    self.lock.release()

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

def writable(self):
    return not self.queue.is_empty()

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

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

ПРИМЕЧАНИЕ. Это попытка упростить вопрос опубликовано здесь


person Martinis Group    schedule 31.05.2013    source источник


Ответы (1)


стандартный ввод доступен для выбора. Поместите stdin в свой диспетчер.

Кроме того, я рекомендую Twisted для будущей разработки любого программного обеспечения, управляемого событиями. Он намного функциональнее, чем asyncore, имеет лучшую документацию, большее сообщество, лучшую производительность и т. д.

person Jean-Paul Calderone    schedule 31.05.2013
comment
Спасибо. Я попытаюсь понять, как использовать стандартный ввод. - person Martinis Group; 01.06.2013
comment
На самом деле у нас уже есть работающий бэкенд Twisted, и это здорово! На самом деле цель создания на основе asyncore заключалась в том, чтобы уйти от Twisted, когда вышел Python 3, чтобы мы могли двигаться вперед. Совсем недавно я заметил, что он запускает горячий цикл из-за ошибки реализации, что и вызвало мой пост. Хотя мне нравится Twisted, я не согласен с тем, что он хорошо документирован; если бы не это, я бы счел его совершенно бесполезным. Тем не менее, это здорово! (извините за форматирование, я не могу понять, как сделать пустые строки) - person Martinis Group; 01.06.2013
comment
На самом деле я думаю, что это не очень хорошее решение. Пользователь вызывает удаленный запрос чем-то вроде myConnection.remoteCallName(args), который помещает запрос в очередь в потоке асинхронного цикла (отличном от потока основного терминала). Проблема в том, что я не знаю, разбудит ли stdin цикл выбора до или после постановки запроса в очередь. - person Martinis Group; 01.06.2013
comment
Работа вашей программы заключается в отслеживании таких деталей. В стандартном вводе нет ничего особенного, это просто еще один источник событий. - person Jean-Paul Calderone; 01.06.2013
comment
Я имею в виду, что с вашим решением цикл select() проснется, как только пользователь введет команду. Это плохо, потому что цикл затем снова заблокируется до того, как команда пользователя будет поставлена ​​в очередь, и мы вернемся к тому, с чего начали. - person Martinis Group; 03.06.2013
comment
Если у вас есть только один поток, это уже не так. Единственный запущенный поток, который вызвал select(), проснется, как только пользователь введет команду, этот же поток вызовет обработчик для ввода stdin, тот же поток поставит команду в очередь, тот же поток перейдет обратно в asyncore тот же поток вызовет writeable, который найдет команду в очереди (поскольку постановка команды в очередь произошла ранее при выполнении этого единственного потока), и все будет грандиозно. - person Jean-Paul Calderone; 03.06.2013
comment
А, теперь я вижу. Я должен попробовать это. Кроме того, повозившись, я заметил, что реактор выбора Twisted win32 по умолчанию использует тайм-аут 0,01 секунды. Я предполагаю, что это связано с тем, что Windows не может выбирать ничего, кроме сокетов Winsock. Я нашел это интересным, так как использование тайм-аута для решения моей проблемы пришло мне в голову, но казалось неприглядным. Приятно знать, что профессионалы используют простые решения :) - person Martinis Group; 09.06.2013