递归使用asyncio事件循环

时间:2015-09-29 17:54:05

标签: python-3.x python-asyncio

我是asyncio的新手(与python3.4一起使用),我不确定我是否应该使用它。我在this thread中看到它可以用来每n秒执行一次函数(在我的情况下为ms),而不必深入研究线程。

我每隔n ms使用它通过基本串行协议从激光传感器获取数据,直到我得到m个样本。

以下是我的功能定义:

def countDown( self, 
               loop, 
               funcToDo, 
               *args, 
               counter = [ 1 ],
               **kwargs ):
    """ At every call, it executes funcToDo ( pass it args and kwargs )
        and count down from counter to 0. Then, it stop loop """
    if counter[ 0 ] == 0:
        loop.stop() 
    else:
        funcToDo( *args, **kwargs )
        counter[ 0 ] -= 1


def _frangeGen( self, start = 0, stop = None, step = 1 ):
    """ use to generate a time frange from start to stop by step step """
    while stop is None or start < stop:
        yield start
        start += step

def callEvery( self, 
               loop, 
               interval, 
               funcToCall, 
               *args, 
               now = True, 
               **kwargs ):
    """ repeat funcToCall every interval sec in loop object """
    nb = kwargs.get( 'counter', [ 1000 ] )
    def repeat( now = True,
                times = self._frangeGen( start = loop.time(),
                                         stop=loop.time()+nb[0]*interval,
                                         step = interval ) ):
        if now:
            funcToCall( *args, **kwargs )
        loop.call_at( next( times ), repeat )

    repeat( now = now )

这就是我使用它的方式(getAllData是管理串行通信的功能):

ts = 0.01
nbOfSamples = 1000
loop = asyncio.get_event_loop()
callEvery( loop, ts, countDown, loop, getAllData, counter = [nbOfSamples] )  
loop.run_forever()

我想把这个bloc放到一个函数中并按照我想要的方式调用它,就像这样:

for i in range( nbOfMeasures ):
    myFunction()
    processData() 

但第二次测试不会调用getAllData 1000次,只调用两次,有时是三次。有趣的事实是,有两次我得到的数据和我想要的一样多。我不太懂,我在文档中找不到任何内容,所以我请求你的帮助。欢迎任何解释或更简单的方法:)

2 个答案:

答案 0 :(得分:1)

你的事情太复杂了,一般来说,当你有一个事件循环时做递归就是糟糕的设计。 只有在您使用协同程序时,import asyncio as aio def get_laser_data(): """ get data from the laser using blocking IO """ ... @aio.coroutine def get_samples(loop, m, n): """ loop = asyncio event loop m = number of samples n = time between samples """ samples = [] while len(samples) < m: sample = yield from loop.run_in_executor(None, get_laser_data) samples.append(sample) yield from aio.sleep(n) return samples @aio.coroutine def main(loop): for i in range(nbOfMeasures): samples = yield from get_samples(loop, 1000, 0.01) ... loop = aio.get_event_loop() loop.run_until_complete(main(loop)) loop.close() 才是有趣。这是一种方法:

asyncio

如果您对此感到困惑,请考虑阅读有关aio.sleep的一些教程/文档。

但我想指出,必须使用线程从激光传感器获取数据。在运行事件循环的同一线程中执行任何阻塞IO将阻止循环并抛弃yield from loop.run_in_executor(None, get_laser_data)。这就是get_laser_data正在做的事情。它在一个单独的线程中运行recursive=False函数。

答案 1 :(得分:0)

在python 3.5中,您可以使用async for语法并创建asynchronous iterator来控制时间范围。它必须实现__aiter____anext__方法:

class timeframes(collections.AsyncIterator):

    def __init__(self, steps, delay=1.0, *, loop=None):
        self.loop = asyncio.get_event_loop() if loop is None else loop
        self.ref = self.loop.time()
        self.delay = delay
        self.steps = steps
        self.iter = iter(range(steps))

    async def __anext__(self):
        try:
            when = self.ref + next(self.iter) * self.delay
        except StopIteration:
            raise StopAsyncIteration
        else:
            future = asyncio.Future()
            self.loop.call_at(when, future.set_result, None)
            await future
            return self.loop.time()

    async def __aiter__(self):
        return self

这是一个模拟执行的协程:

async def simulate(steps, delay, execution):
    # Prepare timing
    start = loop.time()
    expected = steps * delay - delay + execution
    # Run simulation
    async for t in timeframes(steps, delay):
        await loop.run_in_executor(None, time.sleep, execution)
    # Return error
    result = loop.time() - start
    return result - expected

这就是你在linux操作系统上得到的结果:

>>> loop = asyncio.get_event_loop()
>>> simulation = simulate(steps=1000, delay=0.020, execution=0.014)
>>> error = loop.run_until_complete(simulation)
>>> print("Overall error = {:.3f} ms".format(error * 1000))
Overall error = 1.199 ms

在Windows操作系统上有所不同(见this answer),但事件循环将赶上,整体错误不应超过15毫秒。