简体   繁体   English

递归使用asyncio事件循环

[英]Use of asyncio event loop recursively

I am new to asyncio ( used with python3.4 ) and I am not sure I use it as one should. 我是asyncio的新手(与python3.4一起使用),我不确定我是否应该按需使用它。 I have seen in this thread that it can be use to execute a function every n seconds (in my case ms) without having to dive into threading. 我在该线程中看到,它可以每隔n秒(以我的情况为ms)执行一个函数,而不必深入研究线程。

I use it to get data from laser sensors through a basic serial protocol every n ms until I get m samples. 我每隔n毫秒使用它通过基本串行协议从激光传感器获取数据,直到获得m个样本。

Here is the definition of my functions : 这是我的功能的定义:

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 )

And this is how I use it (getAllData is the function that manage serial communication) : 这就是我的用法(getAllData是管理串行通信的函数):

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

I want to put that bloc into a function and call it as often as I want, something like this : 我想将该块放入函数中并根据需要多次调用它,如下所示:

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

But the second test does not call getAllData 1000 times, only twice, sometimes thrice. 但是第二个测试不会调用getAllData 1000次,只能调用两次,有时三次。 The interesting fact is one time in two I get as much data as I want. 有趣的事实是,我有两分之一地获得了所需的数据。 I don't really understand, and I can't find anything in the docs, so I am asking for your help. 我不太了解,在文档中也找不到任何内容,因此,我需要您的帮助。 Any explanation or an easier way to do it is gladly welcome :) 任何解释或更简单的方法都欢迎您:)

You are complicating things too much and, generally speaking, doing recursion when you have an event loop is bad design. 您使事情复杂化了很多,通常来说,在发生事件循环时进行递归是不好的设计。 asyncio is fun only when you make use of coroutines. 仅当使用协程时, asyncio才是有趣的。 Here's one way of doing it: 这是一种实现方法:

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()

If you are completely confused by this, consider reading some tutorials/documentation about asyncio . 如果您对此感到完全困惑,请考虑阅读一些有关asyncio教程/文档。

But I would like to point out that you must use a thread to get the data from the laser sensor. 但我想指出,您必须使用线程从激光传感器获取数据。 Doing any blocking IO in the same thread that the event loop is running will block the loop and throw off aio.sleep . 在事件循环正在运行的同一线程中执行任何阻塞IO将阻塞该循环并抛出aio.sleep This is what yield from loop.run_in_executor(None, get_laser_data) is doing. 这就是yield from loop.run_in_executor(None, get_laser_data)的结果。 It's running the get_laser_data function in a separate thread. 它在单独的线程中运行get_laser_data函数。

In python 3.5, you can make use of the async for syntax and create an asynchronous iterator to control your time frames. 在python 3.5中,您可以使用async for语法,并创建一个异步迭代器来控制时间范围。 It has to implement the __aiter__ and __anext__ methods: 它必须实现__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

Here's a coroutine that simulates an execution: 这是一个模拟执行的协程:

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

And this is the kind of result you'll get on a linux OS: 这是您在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

It is different on a windows OS (see this answer ) but the event loop will catch up and the overall error should never exceed 15 ms. 在Windows操作系统上,这是不同的(请参阅此答案 ),但是事件循环将赶上,并且总错误不应超过15毫秒。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM