#python #terminal #python-asyncio #countdown
#python #терминал #python-asyncio #обратный отсчет
Вопрос:
использование: python 3.8 в Windows 10
Я работаю над сценарием, который выполняется в цикле while. В конце цикла я хочу, чтобы он подождал 5 секунд, пока пользователь введет ввод, а затем перезапустит или завершит работу, если они не вводят ввод.
Я никогда не использовал его раньше, но я предположил, что asyncio будет полезен, потому что он позволяет определять ожидаемые объекты. Однако объединение усилий оказывается сложнее, чем я ожидал.
import keyboard, asyncio, time as t
async def get():
keyboard.record('enter', True)
return True
async def countdown(time):
while time > 0:
print(f'This program will exit in {int(time)} seconds. Press enter to start over.', end='r')
await asyncio.sleep(1)
# t.sleep(1)
time -= 1
print(f'This program will exit in 0 seconds. Press enter to start over.', end='r')
return False
async def main():
running = True
while running:
# other code
clock = asyncio.create_task(countdown(5))
check = asyncio.create_task(get())
done, pending = await asyncio.wait({clock, check}, return_when=asyncio.FIRST_COMPLETED)
running = next(iter(done)).result()
print(running, end='r')
print('bye')
asyncio.run(main())
В нынешнем виде процесс не завершается, если я подожду пять секунд. Он также не заметно отсчитывает время (мое лучшее и самое смешное предположение заключается в том, что он может слишком быстро зацикливаться в main
? Попробуйте удерживать нажатой клавишу «enter» — с печатью и без печати running
).
Кроме того, когда я переключаюсь time.sleep
, отображение работает нормально, но не похоже, что countdown
функция когда-либо возвращается.
Ранее я пытался использовать input
оператор вместо keyboard.record
; который блокирует. Я также пытался использовать asyncio.wait_for
; но тайм-аут так и не наступил, хотя, опять же, была зарегистрирована клавиша «enter» (тем не менее, он не напечатал бы обратный отсчет, даже если бы он работал). Я также пытался asyncio.as_completed
, но мне не удалось извлечь что-либо полезное из итерируемого. Хотя я рад, что ошибаюсь!
Кроме того, бонусные баллы, если вы можете обобщить обратный отсчет до нецелых временных интервалов <3
Подход wait_for
:
async def main():
running = True
while running:
try:
running = await asyncio.wait_for(get(), 5)
except asyncio.TimeoutError:
running = False
print(running)
print('bye')
asyncio.run(main())
as_completed
async def main():
running = True
while running:
## other code
clock = asyncio.create_task(countdown(5))
check = asyncio.create_task(get())
for f in asyncio.as_completed({check, clock}):
# print(f.cr_frame)
# print(f.cr_await, f.cr_running, f.cr_origin, f.cr_frame, f.cr_code)
print(f.cr_await, f.cr_running, f.cr_origin)
print('bye')
Кроме того, бонусные баллы, если вы можете обобщить обратный отсчет до нецелочисленного времени: P
приветствия!
Комментарии:
1. Ваша основная проблема заключается в том, что используемая вами библиотека клавиатуры не является асинхронной, о чем свидетельствует тот факт, что
get()
функция ничего не ожидает. Асинхронная функция, которая не ожидает, является асинхронной только по имени и не допускает распараллеливания.
Ответ №1:
Если вы посмотрите в документации для keyboard.record, там написано:
Примечание: это блокирующая функция.
Вот почему ваш процесс не завершился через 5 секунд. Он блокировался keyboard.record('enter', True)
. Если вы собираетесь использовать клавиатурный модуль, вам нужно создать хук для клавиши «enter». Я собрал быструю демонстрацию вместе с вашим кодом:
import asyncio
import keyboard
class Program:
def __init__(self):
self.enter_pressed = asyncio.Event()
self._loop = asyncio.get_event_loop()
async def get(self):
await self.enter_pressed.wait()
return True
@staticmethod
async def countdown(time):
while time > 0:
print(f'This program will exit in {int(time)} seconds. Press enter to start over.')
await asyncio.sleep(1)
# t.sleep(1)
time -= 1
print(f'This program will exit in 0 seconds. Press enter to start over.')
return False
def notify_enter(self, *args, **kwargs):
self._loop.call_soon_threadsafe(self.enter_pressed.set)
async def main(self):
running = True
while running:
# other code
keyboard.on_press_key('enter', self.notify_enter)
self.enter_pressed.clear()
clock = asyncio.create_task(self.countdown(5))
check = asyncio.create_task(self.get())
done, pending = await asyncio.wait({clock, check}, return_when=asyncio.FIRST_COMPLETED)
keyboard.unhook('enter')
for task in pending:
task.cancel()
running = next(iter(done)).result()
print(running)
print('bye')
async def main():
program = Program()
await program.main()
asyncio.run(main())
Создается обратный notify_enter
вызов, который устанавливает asyncio.Event
значение всякий раз, когда он запускается. get()
Задача ожидает запуска этого события перед его завершением. Поскольку я не знал, что делает ваш другой код, мы не привязываем перехват к событию key_down клавиши enter до тех пор, пока вы не дождетесь выполнения обеих задач, и мы не отвяжем его сразу после завершения одной из задач. Я завернул все в класс, чтобы событие было доступно при обратном вызове, поскольку нет способа передать параметры.
Комментарии:
1. Обратный
on_press_key
вызов (notify_enter
) вызывается в отдельном потоке, поэтому вы должны использоватьcall_soon_threadsafe
для его вызова, т. е.self._loop.call_soon_threadsafe(self.enter_pressed.set)
.2. Спасибо, W1ndstorm! Вы также устанавливаете хук в идеальном месте. Мне удалось включить ваше исправление, превратив
notify_enter
функцию в оболочку для отдельной функции, которая обрабатывает обратный вызов и устанавливает перехват. Я назначилenter_pressed
непосредственно передon_press_key
этим и передал его в get.3. @user4815162342, что будет сохранено
self._loop
в этом сценарии? И как мне узнать, когда я нахожусь в другом потоке? Спасибо.4. @kendfss Вы бы сохранили цикл событий, используемый asyncio, выполнив
self._loop = asyncio.get_event_loop()
вProgram
‘s__init__
. Обратный вызов, зарегистрированный вon_press_key
, должен вызываться другим потоком, потому что ваш поток продолжает выполнять другие действия, поэтомуkeyboard
«очевидно» имеет свой собственный фоновый поток, который отслеживает клавиатуру и вызывает зарегистрированные обратные вызовы.5. @user4815162342 хороший улов, я обновил пример