2

有没有办法手动退出三重奏无限循环,例如三重奏教程中的回声客户端,https://trio.readthedocs.io/en/latest/tutorial.html#an-echo-client,除了使用Ctrl-C或使用超时?

我的想法是使用从另一个 python 脚本调用 echo 客户端,并且能够使用相同的 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)

我发现我无法在 python REPL 之后输入任何命令trio.run()来手动更改,并且想知道我是否从另一个脚本调用此 echo 客户端,如何在托儿所中FLAG准确触发?cancel_scope.cancel()或者,还有更好的方法?非常感谢所有帮助。谢谢。

4

2 回答 2

3

如果您想使用键盘输入退出,这里有适用于 Linux 和 Mac OS X 的解决方案。您可以使用 Python msvcrt模块在 Windows 上执行类似的操作。

echo-client.py我从Trio 教程中复制并在添加的三个代码块上添加了“新”注释。在 REPL 中,您可以键入 'q' 以取消 Nursery 范围并退出:

# -- 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 共享其文件描述。设置为非阻塞也会设置为非阻塞作为副作用,这可能会导致函数出现问题(在我的例子中是截断)。stdoutstderrstdinstdoutprint

进程间通信

这是一个使用套接字从另一个进程中取消一个 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)
于 2019-06-18T01:53:54.990 回答
1

有很多方法可以做到这一点。为什么不直接使用Ctrl-C?对我来说似乎完全有效。

如果您真的不想使用Ctrl-C,那么您将需要一个侦听输入和更新的函数(或者直接退出程序;老实说FLAG,我认为您根本不需要这里的逻辑)。FLAG

例如,您可以有一个从文件轮询/从数据库读取/侦听终端输入等的函数,并使其并行运行。侦听器应在同一 Python 脚本中作为单独的工作人员运行。但取决于您选择如何执行此操作,更改外部输入的函数可以是独立脚本

于 2019-06-17T13:28:10.213 回答