如何编写适用于线程或协程并在确定的时间内完成的python代码?

时间:2018-08-12 10:37:28

标签: python multithreading python-asyncio

“确定性时间”是什么意思?例如,AWS提供服务“ AWS Lambda”。由于lambda函数具有时间限制而开始的进程,此lambda函数将停止执行,并假定任务已完成但有错误。和示例任务-将数据发送到http端点。根据到http端点的网络连接或其他因素,发送数据的过程可能需要很长时间。如果我需要将相同的数据发送到多个端点,则整个处理时间将是one process timeendpoints amount。这增加了lambda函数在将所有数据发送到所有端点之前将被停止的机会。 为了解决这个问题,我需要使用线程以并行方式将数据发送到不同的端点。

线程问题-无法停止启动的线程。如果http请求所花费的时间超过了lambda函数的时间限制,则lambda函数将被中止并返回错误。因此,如果需要的时间超出预期,我需要对http请求使用超时,以中止它。

如果http请求将被超时取消或端点将返回错误,则我需要将未处理的数据保存在某个位置,以免丢失数据。可以预测保存未处理数据所需的时间,因为我控制着要保存数据的存储空间。

最后一个消耗时间的部分-调度线程的过程或循环executor.submit()。如果只有一个端点或端点数量很少,那么消耗的时间将很小。并且没有必要控制它。但是,如果我处理了许多端点,则必须考虑到这一点。

因此,全日制基本上包括:

  • 调度线程
  • http请求执行
  • 保存未处理的数据

有一个示例我可以如何使用线程来管理时间

import concurrent.futures
from functools import partial

import requests
import time

start = time.time()

def send_data(data):
    host = 'http://127.0.0.1:5000/endpoint'
    try:
        result = requests.post(host, json=data, timeout=(0.1, 0.5))
        # print('done')
        if result.status_code == 200:
            return {'status': 'ok'}
        if result.status_code != 200:
            return {'status': 'error', 'msg': result.text}
    except requests.exceptions.Timeout as err:
        return {'status': 'error', 'msg': 'timeout'}


def get_data(n):
    return {"wait": n}


def done_cb(a, b, future):
    pass # save unprocessed data


def main():
    executor = concurrent.futures.ThreadPoolExecutor()
    futures = []
    max_time = 0.5

    for i in range(1):
        future = executor.submit(send_data, *[{"wait": 10}])
        future.add_done_callback(partial(done_cb, 2, 3))
        futures.append(future)
        if time.time() - s_time > max_time:
            print('stopping creating new threads')
            # save unprocessed data
            break

    try:
        for item in concurrent.futures.as_completed(futures, timeout=1):
            item.result()
    except concurrent.futures.TimeoutError as err:
        pass

我在考虑如何使用asyncio库而不是线程来执行相同的操作。

import asyncio
import time
from functools import partial

import requests

start = time.time()


def send_data(data):
    ...

def get_data(n):
    return {"wait": n}

def done_callback(a,b, future):
    pass # save unprocessed data

def main(loop):
    max_time = 0.5
    futures = []
    start_appending = time.time()
    for i in range(1):
        event_data = get_data(1)
        future = (loop.run_in_executor(None, send_data, event_data))
        future.add_done_callback(partial(done_callback, 2, 3))
        futures.append(future)

        if time.time() - s_time > max_time:
            print('stopping creating new futures')
            # save unprocessed data
            break


    finished, unfinished = loop.run_until_complete(
        asyncio.wait(futures, timeout=1)
    )


_loop = asyncio.get_event_loop()
result = main(_loop)

功能send_data()与以前的代码相同。 因为请求库不是异步代码,所以我使用run_in_executor()创建将来的对象。我遇到的主要问题是,done_callback()在线程启动但执行者完成任务时未执行。但是只有在asyncio.wait()表达式会“处理”期货时。

基本上,我在寻找开始执行异步的未来的方式,例如ThreadPoolExecutor开始执行线程,而不是等待asyncio.wait()表达式调用done_callback()。如果您还有其他想法,如何编写可与线程或协程配合使用并在确定时间内完成的python代码。请分享,我将很高兴阅读它们。

还有其他问题。如果线程或将来完成了工作,它可以返回结果,我可以在done_callback()中使用该结果,例如,根据结果返回的ID从队列中删除消息。但是,如果线程或将来被取消,我将没有结果。而且我必须使用functools.partial()传递done_callback其他数据,这可以帮助我了解此回调被调用了哪些数据。如果传递的数据很小,这不是问题。如果数据很大,我需要将数据放入array / list / dictionary,然后仅将回调传递给数组的索引,或者将“完整数据:放入回调”中。

我能以某种方式访问​​从done_callback()传递到future / thread的变量,该变量是在取消的future / thread上触发的吗?

1 个答案:

答案 0 :(得分:0)

您可以使用asyncio.wait_for等待将来(或与asyncio.gather组合使用的将来),并在超时的情况下取消它们。与线程不同,asyncio支持取消,因此您可以随时取消任务,并且它将在它进行的第一个阻塞调用(通常是网络调用)中被取消。

请注意,要使其正常工作,您应该对HTTP使用aiohttp之类的异步库。尝试使用requestsrun_in_executor与asyncio结合起来似乎适用于简单的任务,但它不会给您带来使用asyncio的好处,例如能够产生大量任务而不会妨碍操作系统或取消的可能性。