LenRem
LenRem

Reputation: 63

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?

Upvotes: 6

Views: 2984

Answers (1)

user4815162342
user4815162342

Reputation: 155216

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)

Upvotes: 6

Related Questions