Прерывание raw_input в искривленной программе

Я буду ссылаться на это объяснение и на это обходной путь:

Итак, что я делаю:

def interrupted(signum, stackframe):
    log.warning('interrupted > Got signal: %s', signum)
    menu.quitMenu = True # to stop my code

signal.signal(signal.SIGINT, interrupted) # Handle KeyboardInterrupt

Проблема в том, что, хотя меню уведомлено о том, что оно должно остановиться, и оно скоро это сделает, оно не может сделать это сейчас, так как застряло в raw_input:

def askUser(self):
    current_date   = datetime.now().isoformat(' ')
    choice = raw_input('%s > ' % current_date)
    return choice

Таким образом, поскольку Twisted удаляет обработчик прерывания по умолчанию, raw_input не останавливается. Мне все еще нужно нажать enter после ^C, чтобы он остановился.

Как принудительно остановить raw_input, без установки обработчика прерывания по умолчанию, который является источником проблем в скрученном контексте (поскольку скрученный сам по себе не ожидает прерывания)

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

Есть ли для этого общепринятый скрученный шаблон?

РЕДАКТИРОВАТЬ

Это полный тестовый код:

from datetime import datetime

class Menu:

    def __init__(self):
        self.quitMenu = False

    def showMenu(self):
        print '''

A) Do A
B) Do B

'''

    def askUser(self):
        current_date   = datetime.now().isoformat(' ')
        choice = raw_input('%s > Please select option > ' % current_date)
        print
        return choice

    def stopMe(self):
        self.quitMenu = True

    def alive(self):
        return self.quitMenu == False

    def doMenuOnce(self):
        self.showMenu()
        choice = self.askUser()
        if not self.alive() : # Maybe somebody has tried to stop the menu while in askUser
            return
        if   choice == 'A' : print 'A selected'
        elif choice == 'B' : print 'B selected'
        else               : print 'ERR: choice %s not supported' % (choice)

    def forever(self):
        while self.alive():
            self.doMenuOnce()

from twisted.internet import reactor, threads
import signal

class MenuTwisted:

    def __init__(self, menu):
        self.menu = menu
        signal.signal(signal.SIGINT, self.interrupted) # Handle KeyboardInterrupt

    def interrupted(self, signum, stackframe):
        print 'Interrupted!'
        self.menu.stopMe()

    def doMenuOnce(self):
        threads.deferToThread(self.menu.doMenuOnce).addCallback(self.forever)

    def forever(self, res=None):
        if self.menu.alive() :
            reactor.callLater(0, self.doMenuOnce)
        else : 
            reactor.callFromThread(reactor.stop)

    def run(self):
        self.forever()
        reactor.run()

Который я могу запустить двумя разными способами.

Обычный способ:

menu = Menu()
menu.forever()

Нажатие ^C немедленно останавливает программу:

A) Do A
B) Do B


2013-12-03 11:00:26.288846 > Please select option > ^CTraceback (most recent call last):
  File "twisted_keyboard_interrupt.py", line 72, in <module>
    menu.forever()
  File "twisted_keyboard_interrupt.py", line 43, in forever
    self.doMenuOnce()
  File "twisted_keyboard_interrupt.py", line 34, in doMenuOnce
    choice = self.askUser()
  File "twisted_keyboard_interrupt.py", line 22, in askUser
    choice = raw_input('%s > Please select option > ' % current_date)
KeyboardInterrupt

Как и ожидалось.

Кривой путь:

menu = Menu()
menutw = MenuTwisted(menu)
menutw.run()

Нажатие ^C произведет:

A) Do A
B) Do B


2013-12-03 11:04:18.678219 > Please select option > ^CInterrupted!

Но askUser на самом деле не прерывается: мне все еще нужно нажать enter, чтобы raw_input закончил.


person blueFast    schedule 02.12.2013    source источник


Ответы (1)


Правильный способ справиться с этим - handle консольный ввод асинхронно, вместо того, чтобы пытаться сделать функцию блокирующего ввода прерываемой. Другими словами, raw_input в корне неверное решение проблемы, над которой вы работаете.

Однако, если вы действительно просто хотите понять, что здесь происходит, хитрость заключается в том, что после вызова reactor.callFromThread(reactor.stop) вы должны каким-то образом предложить raw_input выйти; нормально не пойдет. Однако, поскольку вы запускаете его в потоке, на самом деле его вообще нельзя прервать, потому что в Python можно прервать только основной поток. Поэтому я думаю, что то, что вы хотите, на самом деле может быть невозможным. Я полагал, что, возможно, закрытие sys.stdin могло бы выбить ковер из-под raw_input, даже если бы оно было в потоке, но похоже, что лежащие в основе библиотеки делают что-то более хитрое, чем просто чтение из FD, поэтому закрытие не приносит пользы.

person Glyph    schedule 04.12.2013