Run infinite loop in background and access class variable from the loop, with Python asyncio

Question:

I have a class Sensor which has one numeric value num. It has an async method update, which updates the num every second in an infinite loop.

I want to initiate the class and call the method update, keep it running and return the control to the main program to access the value of the num after some time. I am using asyncio but do not know how to invoke the method such that the concurrent running of the loop and variable access is feasible.

Code:

import asyncio
import time


class Sensor:
    def __init__(self, start=0):
        self.num = start

    async def update(self):
        print(f"Starting Updates{self.num}")
        while True:
            self.num += 1
            print(self.num)
            await asyncio.sleep(1)


if __name__ == "__main__":
    print("Main")
    sensor = Sensor()
    # asyncio.run(sensor.update())
    # asyncio.ensure_future(sensor.update())
    future = asyncio.run_coroutine_threadsafe(sensor.update(), asyncio.new_event_loop())
    print("We are back")
    print(f"current value: {sensor.num}")
    time.sleep(4)
    print(f"current value: {sensor.num}")

This gives me output of 0 before and after waiting for 4 seconds, which means the update method is not running behind. run() does not return the control at all.

Which method should I call to invoke the infinite loop in the background?

Asked By: harvpan

||

Answers:

To use asyncio.run_coroutine_threadsafe, you need to actually run the event loop, in a separate thread. For example:

sensor = Sensor()
loop = asyncio.new_event_loop()
threading.Thread(target=loop.run_forever).start()
future = asyncio.run_coroutine_threadsafe(sensor.update(), loop)
...
loop.call_soon_threadsafe(loop.stop)
Answered By: user4815162342

According to documents for running tasks concurrently, you should use gather. For example, your code will be something like this:

import asyncio
import time


class Sensor:
    def __init__(self, start=0):
        self.num = start

    async def update(self):
        print(f"Starting Updates{self.num}")
        while True:
            self.num += 1
            print(self.num)
            await asyncio.sleep(1)


async def print_value(sensor):
    print("We are back")
    print(f"current value: {sensor.num}")
    await asyncio.sleep(4)
    print(f"current value: {sensor.num}")


async def main():
    print("Main")
    sensor = Sensor()
    await asyncio.gather(sensor.update(), print_value(sensor))

if __name__ == "__main__":
    asyncio.run(main())
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.