Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python asyncio.Event.wait() not responding to event.set()

The plan is to have several IO routines running "concurrently" (specifically on a Raspberry Pi, manipulating IO pins and running an SPI interface at the same time). I try to use asyncio to make this happen. However, my simple try-out refuses to run.
This is a reduced version of the code, leaving out the IO pin details:

"""\
Reduced problem representation:
this won't run because GPIO details have been left out
"""

import RPi.GPIO as gpio
import asyncio

GPIO_PB = 12         # Define pushbutton channel

async def payload():
    """ Provides some payload sequence using asyncio.sleep() """
    #Payload action
    await asyncio.sleep(1)
    #Payload action
    await asyncio.sleep(1)

class IOEvent(asyncio.locks.Event):
    """\
    Create an Event for asyncio, fired by a callback from GPIO
    The callback must take a single parameter: a gpio channel number
    """
    def __init__(self, ioChannel, loop):
        super().__init__(loop = loop)
        self.io = ioChannel

    def get_callback(self):
        "The callback is a closure that knows self when called"
        def callback( ch ):
            print("callback for channel {}".format(ch))
            if ch == self.io and not self.is_set():
                print(repr(self))
                self.set()
                print(repr(self))
        return callback

async def Worker(loop, event):
    print("Entering Worker: {}".format(repr(loop)))
    while loop.is_running():
        print("Worker waiting for {}".format(repr(event)))
        await event.wait()
        print("Worker has event")
        event.clear()
        await payload()
        print("payload ended")

loop = asyncio.get_event_loop()

# Create an event for the button
pb_event = IOEvent( GPIO_PB, loop)

# register the pushbutton's callback
# Pushing the button calls this callback function
gpio.add_event_callback( GPIO_PB, pb_event.get_callback() )

try:
    asyncio.ensure_future(Worker(loop, pb_event))
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    print("Closing Loop")
    loop.stop()
    loop.close()

The output I get is like this:

Entering Worker: <_UnixSelectorEventLoop running=True closed=False debug=False>
Worker waiting for <__main__.IOEvent object at 0x76a2a950 [unset]>
callback for channel 12
<__main__.IOEvent object at 0x76a2a950 [unset,waiters:1]>
<__main__.IOEvent object at 0x76a2a950 [set,waiters:1]>
callback for channel 12

These lines show the pushbutton repeatedly and correctly firing its callback routine. The first time it calls the set() funtion as expected. The event used for the wait() call and the set() call are the same. But the message "Worker has event", after the await event.wait() call never appears.

I looked at PyQt5 and asyncio: yield from never finishes, but I do not see any other loops than the default loop.

Why does wait() never return? How could I find out?

like image 647
LenRem Avatar asked Feb 16 '18 23:02

LenRem


People also ask

What does an asyncio event mean?

An asyncio event can be used to notify multiple asyncio tasks that some event has happened. An Event object manages an internal flag that can be set to true with the set () method and reset to false with the clear () method. The wait () method blocks until the flag is set to true.

When the first task completes asyncio wait returns two lists?

When the first task completes asyncio.wait returns two lists: done and pending. We can iterate over each of these lists: pending contains tasks that have not been completed.

What is wait_for_reaction_remove in Python?

def wait_for_reaction_remove(bot, emoji=None, *, user=None, timeout=None, message=None, check=None): """Waits for a reaction to be removed by a user from a message within a time period. Made to act like other discord.py wait_for_* functions but is not fully implemented.

Why does my asyncio code block until the task is completed?

For the majority of asyncio examples, where the demonstration code is waiting on a single event, the issue of retaining a task handle until the task completes is not an issue. In these cases, the code blocks until the task complete, in which case the task has already been removed from the event loop when the coroutine returns a value.


1 Answers

Callbacks set by add_event_callback are called from a different thread, as indicated by them being called automatically "in the background". This means that you can't call set on an asyncio.Event directly from a gpio callback, since asyncio classes are not thread-safe.

To wake up an asyncio.Event from a different thread, you can pass event.set to loop.call_soon_threadsafe. In your case, you would change:

self.set()

to:

self._loop.call_soon_threadsafe(self.set)
like image 69
user4815162342 Avatar answered Oct 03 '22 23:10

user4815162342