Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asyncio: Wait for event from other thread

I'm designing an application in Python which should access a machine to perform some (lengthy) tasks. The asyncio module seems to be a good choice for everything that is network-related, but now I need to access the serial port for one specific component. I've implemented kind of an abstraction layer for the actual serial port stuff, but can't figure out how to sensibly integrate this with asyncio.

Following setup: I have a thread running a loop, which regularly talks to the machine and decodes the responses. Using a method enqueue_query(), I can put a query string into a queue, which will then be sent to the machine by the other thread and cause a response. By passing in a threading.Event (or anything with a set() method), the caller can perform a blocking wait for the response. This can then look something like this:

f = threading.Event()
ch.enqueue_query('2 getnlimit', f)
f.wait()
print(ch.get_query_responses())

My goal is now to put those lines into a coroutine and have asyncio handle this waiting, so that the application can do something else in the meantime. How could I do this? It would probably work by wrapping the f.wait() into an Executor, but this seems to be a bit stupid, as this would create a new thread only to wait for another thread to do something.

like image 717
Philipp Burch Avatar asked Oct 07 '15 18:10

Philipp Burch


People also ask

Does Asyncio run on multiple threads?

However, async and threading can run multiple IO operations truly at the same time. Asyncio vs threading: Async runs one block of code at a time while threading just one line of code at a time.

How do you wait for an event in Python?

Using event.wait() When we want a thread to wait for an event, we can use the wait() method on the event object whose internal flag is set to false, which will block the thread until the set() method sets the internal flag of that event object to true. The thread is not blocked if the internal flag is true on entry.

How does Asyncio Wait work?

Introduction to the Python asyncio wait() functionwait() function runs an iterable of awaitables objects and blocks until a specified condition. The asyncio. wait() function has the following parameters: aws is iterable of awaitable objects that you want to run concurrently.

What is Asyncio Get_event_loop ()?

asyncio. get_event_loop() Get the current event loop. If there is no current event loop set in the current OS thread, the OS thread is main, and set_event_loop() has not yet been called, asyncio will create a new event loop and set it as the current one.


2 Answers

The class Event_ts in Huazuo Gao's answer works well in Python up to 3.9 but not in 3.10. It is because in Python 3.10 the private attribute _loop is initially None.

The following code works in Python 3.10 as well as 3.9 and below. (I added the clear() method as well.)

import asyncio
class Event_ts(asyncio.Event):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self._loop is None:
            self._loop = asyncio.get_event_loop()

    def set(self):
        self._loop.call_soon_threadsafe(super().set)

    def clear(self):
        self._loop.call_soon_threadsafe(super().clear)
like image 103
Tai Sakuma Avatar answered Oct 19 '22 02:10

Tai Sakuma


The simplest way is to do exactly what you suggested - wrap the call to f.wait() in an executor:

@asyncio.coroutine
def do_enqueue():
    f = threading.Event()
    ch.enqueue_query('2 getnlimit', f)
    yield from loop.run_in_executor(None, f.wait)
    print(ch.get_query_responses())

You do incur the overhead of starting up a thread pool (at least for the first call, the pool will stay in memory from that point forward), but any solution that provides an implementation of something like threading.Event() with thread-safe blocking and non-blocking APIs, without relying on any background threads internally, would be quite a bit more work.

like image 41
dano Avatar answered Oct 19 '22 02:10

dano