Как вручную выйти из бесконечного цикла трио, как учебный эхо-клиент трио - PullRequest
1 голос
/ 17 июня 2019

Есть ли способ вручную выйти из бесконечного цикла трио, например, клиент-эхо в руководстве по трио, https://trio.readthedocs.io/en/latest/tutorial.html#an-echo-client, кроме использования Ctrl-C или использования тайм-аутов?

Моя идея состоит в том, чтобы использовать call echo client из другого скрипта python и иметь возможность закрыть его тем же скриптом python произвольно.Я думал об использовании флага (может быть, событие?) В качестве переключателя для запуска cancel_scope.cancel() в детской.Но я не знаю, как активировать переключатель.Ниже приведена моя попытка изменить учебный код клиента эха.

import sys
import trio

PORT = 12345
BUFSIZE = 16384
FLAG = 1 # FLAG is a global variable

async def sender(client_stream):
    print("sender: started")
    while FLAG:
        data = b'async can sometimes be confusing but I believe in you!'
        print(f"sender: sending {data}")
        await client_stream.send_all(data)
        await trio.sleep(1)

async def receiver(client_stream):
    print("recevier: started!")
    while FLAG:
        data = await client_stream.receive_some(BUFSIZE)
        print(f"receiver: got data {data}")
        if not data:
            print("receiver: connection closed")
            sys.exit()

async def checkflag(nursery): # function to trigger cancel()
    global FLAG
    if not FLAG:
        nursery.cancel_scope.cancel()
    else:
        # keep this task running if not triggered, but how to trigger it, 
        # without Ctrl-C or timeout?
        await trio.sleep(1) 

async def parent():
    print(f"parent: connecting to 127.0.0.1:{PORT}")
    client_stream = await trio.open_tcp_stream("127.0.0.1", PORT)
    async with client_stream:
        async with trio.open_nursery() as nursery:
            print("parent: spawning sender ...")
            nursery.start_soon(sender, client_stream)

            print("parent: spawning receiver ...")
            nursery.start_soon(receiver, client_stream)

            print("parent: spawning checkflag...")
            nursery.start_soon(checkflag, nursery)

        print('Close nursery...')
    print("Close stream...")

trio.run(parent)

Я обнаружил, что не могу ввести какие-либо команды в REPL Python после trio.run(), чтобы вручную изменить FLAG, и яИнтересно, если я вызываю этот эхо-клиент из другого скрипта, как именно вызвать cancel_scope.cancel() в детской?Или есть лучший способ?Очень ценю любую помощь.Спасибо.

Ответы [ 2 ]

2 голосов
/ 18 июня 2019

Если вы хотите использовать ввод с клавиатуры для выхода, вот решение для Linux и Mac OS X. Вы можете использовать модуль Python msvcrt , чтобы сделать нечто подобное в Windows.

Я скопировал echo-client.py из учебника по трио и добавил «НОВЫЙ» комментарий к трем добавленным блокам кода. В REPL вы можете набрать 'q', чтобы отменить область питомника и выйти:

# -- NEW
import termios, tty

import sys
import trio

PORT = 12345
BUFSIZE = 16384

# -- NEW
async def keyboard():
    """Return an iterator of characters from stdin."""
    stashed_term = termios.tcgetattr(sys.stdin)
    try:
        tty.setcbreak(sys.stdin, termios.TCSANOW)
        while True:
            yield await trio.run_sync_in_worker_thread(
                sys.stdin.read, 1,
                cancellable=True
            )
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSANOW, stashed_term)

async def sender(client_stream):
    print("sender: started!")
    while True:
        data = b"async can sometimes be confusing, but I believe in you!"
        print("sender: sending {!r}".format(data))
        await client_stream.send_all(data)
        await trio.sleep(1)

async def receiver(client_stream):
    print("receiver: started!")
    while True:
        data = await client_stream.receive_some(BUFSIZE)
        print("receiver: got data {!r}".format(data))
        if not data:
            print("receiver: connection closed")
            sys.exit()

async def parent():
    print("parent: connecting to 127.0.0.1:{}".format(PORT))
    client_stream = await trio.open_tcp_stream("127.0.0.1", PORT)
    async with client_stream:
        async with trio.open_nursery() as nursery:
            print("parent: spawning sender...")
            nursery.start_soon(sender, client_stream)

            print("parent: spawning receiver...")
            nursery.start_soon(receiver, client_stream)

            # -- NEW
            async for key in keyboard():
                if key == 'q':
                    nursery.cancel_scope.cancel()

trio.run(parent)

Вызов tty.setcbreak переводит терминал в небуферизованный режим, поэтому вам не нужно нажимать клавишу возврата, пока программа не получит ввод. Это также предотвращает отображение символов на экране. Кроме того, как следует из названия, он позволяет Ctrl-C работать как обычно.

В блоке finally termios.tcsetattr восстанавливает терминал в любом режиме, в котором он находился до tty.setcbreak. Таким образом, ваш терминал возвращается в нормальное состояние при выходе.

sys.stdin.read создается в отдельном потоке, потому что он должен работать в режиме блокировки (плохо в асинхронном контексте). Причина в том, что stdin делится описанием файла с stdout и stderr. Установка неблокирующего значения stdin также приведет к неблокирующему stdout как побочному эффекту, что может вызвать проблемы с функцией print (усечение в моем случае).

Межпроцессное взаимодействие

Вот базовый пример отмены одного процесса Trio из другого с помощью сокета:

# infinite_loop.py    
import trio    

async def task():     
    while True:       
        print("ping")    
        await trio.sleep(0.5)    

async def quitter(cancel_scope):      
    async def quit(server_stream):    
        await server_stream.receive_some(1024)    
        cancel_scope.cancel()    
    await trio.serve_tcp(quit, 12346)    

async def main():    
    async with trio.open_nursery() as nursery:    
        nursery.start_soon(task)    
        nursery.start_soon(quitter, nursery.cancel_scope)    

trio.run(main)
# slayer.py        
import trio    

async def main():    
    async with await trio.open_tcp_stream("127.0.0.1", 12346) as s:
        await trio.sleep(3)    
        await s.send_all(b'quit')    

trio.run(main)
1 голос
/ 17 июня 2019

Есть много способов сделать это. Почему бы просто не использовать Ctrl-C? Кажется совершенно справедливым для меня.

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

Например, у вас может быть функция, которая опрашивает из файла / читает из базы данных / прослушивает ввод терминала и т. Д., И она работает параллельно. Слушатель должен работать как отдельный рабочий в том же скрипте Python. Но в зависимости от того, как вы решите это сделать, функция, которая изменяет внешний ввод, может быть независимым сценарием

...