Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asyncio event_loop declared outside of a class with asyncio.coroutine methods fails with "AttributeError: 'NoneType' object has no attribute 'select'"

Tags:

python

class

Exploring Python 3.4.0's asyncio module, I am attempting to create a class with asyncio.coroutine methods that are called from an event_loop outside of the class.

My working code is below.

import asyncio

class Foo():
    @asyncio.coroutine
    def async_sleep(self):
        print('about to async sleep')
        yield from asyncio.sleep(1)

    @asyncio.coroutine
    def call_as(self):
        print('about to call ass')
        yield from self.async_sleep()

    def run_loop(self):
        loop = asyncio.get_event_loop()
        loop.run_until_complete(self.call_as())
        print('done with loop')
        loop.close()

a = Foo()
a.run_loop()

loop = asyncio.get_event_loop()
loop.run_until_complete(a.call_as())

The call to a.run_loop() provides output as expected:

python3 async_class.py
about to call ass
about to async sleep
done with loop

However as soon as the event_loop attempts to process a.call_as() I get the following Traceback:

Traceback (most recent call last):
  File "async_class.py", line 26, in <module>
    doop.run_until_complete(asyncio.async(a.call_ass()))
  File "/opt/boxen/homebrew/opt/pyenv/versions/3.4.0/lib/python3.4/asyncio/base_events.py", line 203, in run_until_complete
self.run_forever()
  File "/opt/boxen/homebrew/opt/pyenv/versions/3.4.0/lib/python3.4/asyncio/base_events.py", line 184, in run_forever
self._run_once()
  File "/opt/boxen/homebrew/opt/pyenv/versions/3.4.0/lib/python3.4/asyncio/base_events.py", line 778, in _run_once
event_list = self._selector.select(timeout)
AttributeError: 'NoneType' object has no attribute 'select'

I have attempted wrapping a.call_as() in an asyncio.Task(), asyncio.async() and the failure is the same.

like image 803
shiggiddie Avatar asked Apr 25 '14 16:04

shiggiddie


2 Answers

As it turns out, the issue was with the context of the event loop.

asyncio magically creates an event loop for a thread at runtime. This event loop's context is set when .get_event_loop() is called.

In the above example, a.run_loop sets the event loop inside the context of Foo.run_loop.

One kicker of event loops is that there may only be one event loop per thread, and a given event loop can only process events in its context.

With that in mind, note that the loop = asyncio.get_event_loop() just after a.run_loop is asking to assign the thread's event loop to the __main__ context. Unfortunately, the event loop was already set to the context of Foo.run_loop, so a None type is set for the __main__ event loop.

Instead, it is necessary to create a new event loop and then set that event loop's context to __main__, i.e.

new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)

Only then will an event loop be properly set in the context of __main__, allowing for the proper execution of our now-modified new_loop.run_until_complete(a.call_as())

like image 88
shiggiddie Avatar answered Oct 08 '22 00:10

shiggiddie


It is because you close the eventloop at the end of Foo.run_loop()

From BaseEventLoop.close

Close the event loop. The loop must not be running. Pending callbacks will be lost.

This clears the queues and shuts down the executor, but does not wait for the executor to finish.

This is idempotent and irreversible. No other methods should be called after this one.

like image 35
Markus Bergkvist Avatar answered Oct 08 '22 01:10

Markus Bergkvist