具有两个协程的asyncIO多线程服务器

时间:2018-07-26 21:52:55

标签: python python-3.x multithreading python-asyncio

我正在用Python3编程服务器,该服务器获取屏幕截图并将其通过websocket发送。我有协程用于处理连接,并且我想创建另一个协程以在一定时间间隔拍摄屏幕截图。屏幕快照协程可能会在不同的线程中运行,我将需要将结果传播到具有读写锁定的共享变量中,以便能够发送该结果。我的问题:(如果可能,结果应为多平台)

  1. 如何安排这样的任务?我创建了永远运行的服务器,并且可以创建定期的协程,但是由于某种原因我无法将它们组合在一起。

  2. 将结果从一个线程(或协程,如果服务器是单线程)传播到另一个线程的好方法是什么?

我发现这段代码与此类似,但我无法使其正常工作(第二协程未执行)。有人可以在没有多线程的情况下进行纠正吗?

async def print_var():
    global number
    await asyncio.sleep(2)
    print(number)


async def inc_var():
    global number
    await asyncio.sleep(5)
    number += 1

number = 0

asyncio.get_event_loop().run_until_complete(print_var())
asyncio.async(inc_var)
asyncio.get_event_loop().run_forever()

答案后编辑

最终,经过更多小时的谷歌搜索,我实际上使它可以在单个线程上工作,因此没有竞争状况的危险。 (但我仍然不确定sure_future的作用,以及为什么不在事件循环中调用它。)

users = set()

def register(websocket):
    users.add(websocket)

def unregister(websocket):
    users.remove(websocket)

async def get_screenshot():
    global screenshot
    while True:
        screenshot = screenshot()
        await asyncio.sleep(0.2)

async def server(websocket, path):
    global screenshot
    register(websocket)
    try:
        async for message in websocket:
            respond(screenshot)
    finally:
        unregister(websocket)

def main():
    asyncio.get_event_loop().run_until_complete(
        websockets.serve(server, 'localhost', 6789))
    asyncio.ensure_future(get_screenshot())
    asyncio.get_event_loop().run_forever()

main()

1 个答案:

答案 0 :(得分:0)

在Python 3.7中:

import asyncio

import websockets

CAPTURE_INTERVAL = 1
running = True
queues = set()


async def handle(ws, path):
    queue = asyncio.Queue()
    queues.add(queue)
    while running:
        data = await queue.get()
        if not data:
            break
        await ws.send(data)


def capture_screen():
    # Do some work here, preferably in C extension without holding the GIL
    return b'screenshot data'


async def main():
    global running
    loop = asyncio.get_running_loop()
    server = await websockets.serve(handle, 'localhost', 8765)
    try:
        while running:
            data = await loop.run_in_executor(None, capture_screen)
            for queue in queues:
                queue.put_nowait(data)
            await asyncio.sleep(CAPTURE_INTERVAL)
    finally:
        running = False
        for queue in queues:
            queue.put_nowait(None)
        server.close()
        await server.wait_closed()


if __name__ == '__main__':
    asyncio.run(main())

请注意,这仅用于演示生产者-消费者扇出模式。 queues不是必需的-您可以直接将data发送到server.sockets中的所有main(),而在handle()中则需要担心传入的Websocket消息。例如,客户端可以像这样控制图像压缩率:

import asyncio

import websockets

CAPTURE_INTERVAL = 1
DEFAULT = b'default'
qualities = {}


async def handle(ws, path):
    try:
        async for req in ws:
            qualities[ws] = req
    finally:
        qualities.pop(ws, None)


def capture_screen():
    # Do some work here, preferably in C extension without holding the GIL
    return {
        DEFAULT: b'default screenshot data',
        b'60': b'data at 60% quality',
        b'80': b'data at 80% quality',
    }


async def main():
    loop = asyncio.get_running_loop()
    server = await websockets.serve(handle, 'localhost', 8765)
    try:
        while True:
            data = await loop.run_in_executor(None, capture_screen)
            for ws in server.sockets:
                quality = qualities.get(ws, DEFAULT)
                if quality not in data:
                    quality = DEFAULT
                asyncio.create_task(ws.send(data[quality]))
            await asyncio.sleep(CAPTURE_INTERVAL)
    finally:
        server.close()
        await server.wait_closed()


if __name__ == '__main__':
    asyncio.run(main())