在Python 3中使用asyncio和websockets的长时间延迟

时间:2018-09-24 16:57:38

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

在处理从Websocket服务器推送到我的client.py的数据时,我遇到了很长的(3小时)延迟(编辑:延迟最初是短暂的,然后整天都变长了)。我知道服务器不会延迟它。

例如,每隔5秒我会看到keep_alive日志事件及其相应的时间戳。这样运行平稳。 但是,当我看到日志中处理的数据帧实际上是在服务器发送后的 3小时之后。我是否正在做一些事情来延迟此过程?

我正确地将协程称为'keep_alive'吗? keep_alive只是发送给服务器以保持连接活动的消息。服务器回显该消息。我也记录太多吗?这可能会延迟处理吗(我不这样认为,因为我看到日志记录事件立即发生)。

async def keep_alive(websocket):
                """
                 This only needs to happen every 30 minutes. I currently have it set to every 5 seconds.
                """
                await websocket.send('Hello')   
                await asyncio.sleep(5)

async def open_connection_test():
    """
    Establishes web socket (WSS). Receives data and then stores in csv.
    """
    async with websockets.connect( 
            'wss://{}:{}@localhost.urlname.com/ws'.format(user,pswd), ssl=True, ) as websocket:
        while True:    
            """
            Handle message from server.
            """
            message = await websocket.recv()
            if message.isdigit():
                # now = datetime.datetime.now()
                rotating_logger.info ('Keep alive message: {}'.format(str(message)))
            else:
                jasonified_message = json.loads(message)
                for key in jasonified_message:
                    rotating_logger.info ('{}: \n\t{}\n'.format(key,jasonified_message[key]))    
                """
                Store in a csv file.
                """
                try:            
                    convert_and_store(jasonified_message)
                except PermissionError:
                    convert_and_store(jasonified_message, divert = True)                        
            """
            Keep connection alive.
            """            
            await keep_alive(websocket)

"""
Logs any exceptions in logs file.
"""
try:
    asyncio.get_event_loop().run_until_complete(open_connection())
except Exception as e:
    rotating_logger.info (e)

编辑: 从documentation开始-我认为这可能与它有关-但是我还没有联系好点。

  

max_queue参数设置队列的最大长度   保存传入的消息。默认值为32。0禁用   限制。收到邮件后,邮件就会添加到内存队列中;   然后recv()从该队列中弹出。为了防止过多的记忆   收到消息的速度快于消息的消耗   处理后,队列必须是有界的。如果队列已满,则   协议停止处理传入数据,直到调用recv()为止。在   在这种情况下,各种接收缓冲区(至少在asyncio和   操作系统)将填满,然后TCP接收窗口将缩小,变慢   下行传输以避免丢包。

编辑9/28/2018:我正在测试中没有保持活动消息,这似乎不是问题。可能与convert_and_store()函数有关吗?是否需要先异步定义然后再等待?

def convert_and_store(data, divert = False, test = False):
    if test:
        data = b
    fields = data.keys()
    file_name =  parse_call_type(data, divert = divert)
    json_to_csv(data, file_name, fields)

编辑10/1/2018:似乎保持连接消息和convert_and_store都存在问题;如果我将保持活动消息的时间延长到60秒-那么convert_and_store将每60秒仅运行一次 。因此,convert_and_store正在等待keep_alive()...

3 个答案:

答案 0 :(得分:3)

  

是否可以与convert_and_store()函数相关?

是的,可能是。阻止代码不应直接调用。如果某个函数执行CPU密集型计算1秒钟,则所有异步任务和IO操作都会延迟1秒钟。

执行程序可用于在其他线程/进程中运行阻塞代码:

import asyncio
import concurrent.futures
import time

def long_runned_job(x):
    time.sleep(2)
    print("Done ", x)

async def test():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        for i in range(5):
            loop.run_in_executor(pool, long_runned_job, i)
            print(i, " is runned")
            await asyncio.sleep(0.5)
loop = asyncio.get_event_loop()
loop.run_until_complete(test())

在您的情况下,它应该看起来像这样:

import concurrent.futures

async def open_connection_test():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        async with websockets.connect(...) as websocket:
            while True:    
                ...
                loop.run_in_executor(pool, convert_and_store, args)

已编辑

  

保持连接消息和convert_and_store似乎都存在争议

您可以在后台运行keep_alive

async def keep_alive(ws):
    while ws.open:
        await ws.ping(...)   
        await asyncio.sleep(...)

async with websockets.connect(...) as websocket:
    asyncio.ensure_future(keep_alive(websocket))
    while True:    
        ...

答案 1 :(得分:1)

必须确实为此keep_alive()函数启动一个新线程。

对于async-await,可以保证在继续下一步之前,所有任务都已完成。

因此,await keep_alive(websocket)实际上在这种意义上阻塞了线程。您可能不会在这里等待keep_alive这样的过程才能继续,但是可以肯定的是,那不是您想要的。

实际上,您需要两个时间范围,一个时间用于与服务器通信,一个时间用于使服务器保持活动状态。它们应该分开放置,因为它们位于不同的协程中。

因此,正确的方法是使用Thread,并且在这种情况下不必使用asyncio,请保持简单。

首先,将keep_alive()更改为以下内容。

def keep_alive():
    """
        This only needs to happen every 30 minutes. I currently have it set to every 5 seconds.
    """
    while True:
        websocket.send('Hello') 
        time.sleep(1)

open_connection_test()

async def open_connection_test():
    """
    Establishes web socket (WSS). Receives data and then stores in csv.
    """
    thread = threading.Thread(target=keep_alive, args=())
    thread.daemon = True   # Daemonize
    thread.start()
    async with websockets.connect(...) as websocket:
        ....
        #No need this line anymore.
        #await keep_alive(websocket) 

答案 2 :(得分:0)

我认为这会更清楚,请使用ThreadPoolExecutor使阻止代码在后台运行

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(max_workers=4)

def convert_and_store(data, divert=False, test=False):
    loop = asyncio.get_event_loop()
    loop.run_in_executor(pool, _convert_and_store, divert, test)


def _convert_and_store(data, divert=False, test=False):
    if test:
        data = b
    fields = data.keys()
    file_name = parse_call_type(data, divert=divert)
    json_to_csv(data, file_name, fields)

asyncio发送保持活动的味精演示

async def kepp_alive(websocket):
    while True:
        await websocket.send_str(ping)
        await asyncio.sleep(10)