代码之家  ›  专栏  ›  技术社区  ›  weatherfrog

Python-如何使用异步同时运行多个协程?

  •  39
  • weatherfrog  · 技术社区  · 11 年前

    我正在使用 websockets 库来创建Python 3.4中的websocket服务器。下面是一个简单的echo服务器:

    import asyncio
    import websockets
    
    @asyncio.coroutine
    def connection_handler(websocket, path):
        while True:
            msg = yield from websocket.recv()
            if msg is None:  # connection lost
                break
            yield from websocket.send(msg)
    
    start_server = websockets.serve(connection_handler, 'localhost', 8000)
    asyncio.get_event_loop().run_until_complete(start_server)
    asyncio.get_event_loop().run_forever()
    

    假设我们还想在任何事件发生时向客户端发送消息。为了简单起见,让我们每60秒定期发送一条消息。我们该怎么做?我是说,因为 connection_handler 正在不断等待传入消息,服务器只能采取行动 之后 它收到了客户的消息,对吗?我在这里缺少什么?

    也许这个场景需要一个基于事件/回调的框架,而不是一个基于协程的框架? Tornado ?

    4 回复  |  直到 4 年前
        1
  •  37
  •   Cyril N.    7 年前

    我很惊讶 gather 没有提到。

    从 Python documentation :

    import asyncio
    
    async def factorial(name, number):
        f = 1
        for i in range(2, number + 1):
            print(f"Task {name}: Compute factorial({i})...")
            await asyncio.sleep(1)
            f *= i
        print(f"Task {name}: factorial({number}) = {f}")
    
    async def main():
        # Schedule three calls *concurrently*:
        await asyncio.gather(
            factorial("A", 2),
            factorial("B", 3),
            factorial("C", 4),
        )
    
    asyncio.run(main())
    
    # Expected output:
    #
    #     Task A: Compute factorial(2)...
    #     Task B: Compute factorial(2)...
    #     Task C: Compute factorial(2)...
    #     Task A: factorial(2) = 2
    #     Task B: Compute factorial(3)...
    #     Task C: Compute factorial(3)...
    #     Task B: factorial(3) = 6
    #     Task C: Compute factorial(4)...
    #     Task C: factorial(4) = 24
    
        2
  •  34
  •   Yaroslav Admin    11 年前

    TL;博士 使用 asyncio.ensure_future() 同时运行多个协同程序。


    也许这个场景需要一个基于事件/回调的框架,而不是一个基于协程的框架?龙卷风

    不,你不需要任何其他框架。异步应用程序与同步应用程序的整体思想是,在等待结果时,它不会阻塞。使用协程或回调来实现它并不重要。

    我的意思是,因为connection_handler一直在等待传入消息,所以服务器只能在收到来自客户端的消息后才采取行动,对吗?我在这里缺少什么?

    在同步应用程序中,您将编写如下内容 msg = websocket.recv() ,这将阻止整个应用程序,直到您收到消息(如您所述)。但在异步应用程序中则完全不同。

    当你这样做的时候 msg = yield from websocket.recv() 你会说:暂停执行 connection_handler() 直到 websocket.recv() 会产生一些东西。使用 yield from 内部协程将控制返回到事件循环,因此可以在等待结果时执行其他一些代码 websocket.recv() 。请参阅 documentation 以更好地理解协同工作的方式。

    假设我们还想在任何事件发生时向客户端发送消息。为了简单起见,让我们每60秒定期发送一条消息。我们该怎么做?

    您可以使用 asyncio.async() 在执行 starting event loop .

    import asyncio
    
    import websockets
    
    # here we'll store all active connections to use for sending periodic messages
    connections = []
    
    
    @asyncio.coroutine
    def connection_handler(connection, path):
        connections.append(connection)  # add connection to pool
        while True:
            msg = yield from connection.recv()
            if msg is None:  # connection lost
                connections.remove(connection)  # remove connection from pool, when client disconnects
                break
            else:
                print('< {}'.format(msg))
            yield from connection.send(msg)
            print('> {}'.format(msg))
    
    
    @asyncio.coroutine
    def send_periodically():
        while True:
            yield from asyncio.sleep(5)  # switch to other code and continue execution in 5 seconds
            for connection in connections:
                print('> Periodic event happened.')
                yield from connection.send('Periodic event happened.')  # send message to each connected client
    
    
    start_server = websockets.serve(connection_handler, 'localhost', 8000)
    asyncio.get_event_loop().run_until_complete(start_server)
    asyncio.async(send_periodically())  # before blocking call we schedule our coroutine for sending periodic messages
    asyncio.get_event_loop().run_forever()
    

    这里是一个示例客户端实现。它要求您输入名称,从echo服务器接收它,等待来自服务器的另外两条消息(这是我们的定期消息)并关闭连接。

    import asyncio
    
    import websockets
    
    
    @asyncio.coroutine
    def hello():
        connection = yield from websockets.connect('ws://localhost:8000/')
        name = input("What's your name? ")
        yield from connection.send(name)
        print("> {}".format(name))
        for _ in range(3):
            msg = yield from connection.recv()
            print("< {}".format(msg))
    
        yield from connection.close()
    
    
    asyncio.get_event_loop().run_until_complete(hello())
    

    要点:

    1. 在Python 3.4.4中 asyncio.async() 已重命名为 asyncio.ensure_future() .
    2. 有特殊的调度方法 delayed calls ,但它们不适用于协同工作。
        3
  •  10
  •   gzerone    10 年前

    同样的问题,在我看到这里的完美样本之前,很难找到解决方案: http://websockets.readthedocs.io/en/stable/intro.html#both

     done, pending = await asyncio.wait(
            [listener_task, producer_task],
            return_when=asyncio.FIRST_COMPLETED)  # Important
    

    因此,我可以处理多协程任务,如心跳和redis订阅。

        4
  •  1
  •   Benyamin Jafari    3 年前

    如果您正在使用 Python 3.7及更高版本 你可以使用 asyncio.gather() 和 asyncio.run() 如下所示:

    import asyncio
    
    async def coro1():
        for i in range(1, 6):
            print(i)
            await asyncio.sleep(0)  # switches task every one iteration.
    
    async def coro2():
        for i in range(1, 6):
            print(i * 10)
            await asyncio.sleep(0)  # switches task every one iteration.
    
    async def main():
        await asyncio.gather(
            coro1(),
            coro2(),
        )
    
    asyncio.run(main())
    
    
    ## Or instead of defining the main async function:
    
    futures = [coro1(), coro2()]
    await asyncio.gather(*futures)
    

    否则,如果您正在使用 Python 3.6或3.5 对于同样的结果,也应该处理循环,请执行以下操作:

    import asyncio
    
    async def coro1():
        for i in range(1, 6):
            print(i)
            await asyncio.sleep(0)  # switches task every one iteration.
    
    async def coro2():
        for i in range(1, 6):
            print(i * 10)
            await asyncio.sleep(0)  # switches task every one iteration.
    
    loop = asyncio.get_event_loop()
    futures = [
        asyncio.ensure_future(coro1()),
        asyncio.ensure_future(coro2())
    ]
    loop.run_until_complete(asyncio.gather(*futures))
    loop.close()
    

    输出:

    1
    10
    2
    20
    3
    30
    4
    40
    5
    50
    

    [更新]:

    在中使用新语法 Python 3.11 你不需要 .gather() :

    async def main():
        async with asyncio.TaskGroup() as tg:
            tg.create_task(coro1())
            tg.create_task(coro2())
    
    asyncio.run(main())