Question about asyncio event primitive implementation

Currently asyncio.Event use deque to store waiter futures. But now that future can be awaited multiple times, maybe use a single future will be faster?

class Event:
    def __init__(self):
        self._value = False
        self.fut = asyncio.Future()

    def is_set(self):
        return self._value

    def set(self):
        if not self._value:
            self._value = True
            self.fut.set_result(True)
            self.fut = asyncio.Future()

    def clear(self):
        self._value = False

    async def wait(self):
        if self._value:
            return True
        try:
            await self.fut
            return True

And if a task waiting on that event gets cancelled? What do you supposed happens to the future then?

I think creating task and cancelling task are task’s scope and has nothing to do with Event implementation? Current Event implementation creates future and add to waiters in wait method, if you cancel the task then wait method will never run.

If you cancel a task, you also cancel any Future it’s waiting on. Now, if that future is shared among multiple tasks…

So in this test case from Cpython:

async def test_wait_cancel(self):
    ev = asyncio.Event()

    wait = asyncio.create_task(ev.wait())
    asyncio.get_running_loop().call_soon(wait.cancel)
    with self.assertRaises(asyncio.CancelledError):
        await wait
    self.assertFalse(ev._waiters)

if I cancel the waiter_task, ev.wait will be cancelled, but Future is in the event and not exposed, will that also be cancelled?

What do you mean, it’s not exposed? It will be the awaitable the waiting task would be awaiting on, yes?

I mean if multiple waiters wait for same event:

ev = asyncio.Event()

waiter1 = asyncio.create_task(ev.wait())
waiter2 = asyncio.create_task(ev.wait())
waiter3 = asyncio.create_task(ev.wait())
waiter4 = asyncio.create_task(ev.wait())

If I cancel waiter1 then call ev.set(), other waiters won’t be awakened?

If you cancel waiter1, the shared future will be cancelled, so your call to ev.set() will fail with InvalidStateError because it’s trying to set a result on a cancelled future.

I should mention that I know all this from experience, as I once tried to implement an Event exactly like you described, and failed miserably :slight_smile:

You are right. I understand why each waiter must have its own future now. Thanks for the explanation!

1 Like