RuntimeError: There is no current event loop in thread in async + apscheduler

前端 未结 5 1951
我寻月下人不归
我寻月下人不归 2020-12-12 21:00

I have a async function and need to run in with apscheduller every N minutes. There is a python code below

URL_LIST = [\'\',
            \'

        
相关标签:
5条回答
  • 2020-12-12 21:14

    Since this question continues to appear on the first page, I will write my problem and my answer here.

    I had a RuntimeError: There is no current event loop in thread 'Thread-X'. when using flask-socketio and Bleak.


    Edit: well, I refactored my file and made a class.

    I initialized the loop in the constructor, and now everything is working fine:

    class BLE:
        def __init__(self):
            self.loop = asyncio.get_event_loop()
    
        # function example, improvement of
        # https://github.com/hbldh/bleak/blob/master/examples/discover.py :
        def list_bluetooth_low_energy(self) -> list:
            async def run() -> list:
                BLElist = []
                devices = await bleak.discover()
                for d in devices:
                    BLElist.append(d.name)
                return 'success', BLElist
            return self.loop.run_until_complete(run())
    

    Usage:

    ble = path.to.lib.BLE()
    list = ble.list_bluetooth_low_energy()
    

    Original answer:

    The solution was stupid. I did not pay attention to what I did, but I moved some import out of a function, like this:

    import asyncio, platform
    from bleak import discover
    
    def listBLE() -> dict:
        async def run() -> dict:
            # my code that keep throwing exceptions.
    
        loop = asyncio.get_event_loop()
        ble_list = loop.run_until_complete(run())
        return ble_list
    

    So I thought that I needed to change something in my code, and I created a new event loop using this piece of code just before the line with get_event_loop():

    loop = asyncio.new_event_loop()
    loop = asyncio.set_event_loop()
    

    At this moment I was pretty happy, since I had a loop running.

    But not responding. And my code relied on a timeout to return some values, so it was pretty bad for my app.

    It took me nearly two hours to figure out that the problem was the import, and here is my (working) code:

    def list() -> dict:
        import asyncio, platform
        from bleak import discover
    
        async def run() -> dict:
            # my code running perfectly
    
        loop = asyncio.get_event_loop()
        ble_list  = loop.run_until_complete(run())
        return ble_list
    
    0 讨论(0)
  • 2020-12-12 21:21

    Just pass fetch_all to scheduler.add_job() directly. The asyncio scheduler supports coroutine functions as job targets.

    If the target callable is not a coroutine function, it will be run in a worker thread (due to historical reasons), hence the exception.

    0 讨论(0)
  • 2020-12-12 21:23

    The important thing that hasn't been mentioned is why the error occurs. For me personally, knowing why the error occurs is as important as solving the actual problem.

    Let's take a look at the implementation of the get_event_loop of BaseDefaultEventLoopPolicy:

    class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
        ...
    
        def get_event_loop(self):
            """Get the event loop.
    
            This may be None or an instance of EventLoop.
            """
            if (self._local._loop is None and
                not self._local._set_called and
                isinstance(threading.current_thread(), threading._MainThread)):
                self.set_event_loop(self.new_event_loop())
            if self._local._loop is None:
                raise RuntimeError('There is no current event loop in thread %r.'
                                   % threading.current_thread().name)
            return self._local._loop
    

    You can see that the self.set_event_loop(self.new_event_loop()) is only executed if all of the below conditions are met:

    • self._local._loop is None - _local._loop is not set
    • not self._local._set_called - set_event_loop hasn't been called yet
    • isinstance(threading.current_thread(), threading._MainThread) - current thread is the main one (this is not True in your case)

    Therefore the exception is raised, because no loop is set in the current thread:

    if self._local._loop is None:
        raise RuntimeError('There is no current event loop in thread %r.'
                           % threading.current_thread().name)
    
    0 讨论(0)
  • 2020-12-12 21:32

    In your def demo_async(urls), try to replace:

    loop = asyncio.get_event_loop()
    

    with:

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    
    0 讨论(0)
  • 2020-12-12 21:33

    Use asyncio.run() instead of directly using the event loop. It creates a new loop and closes it when finished.

    This is how the 'run' looks like:

    if events._get_running_loop() is not None:
        raise RuntimeError(
            "asyncio.run() cannot be called from a running event loop")
    
    if not coroutines.iscoroutine(main):
        raise ValueError("a coroutine was expected, got {!r}".format(main))
    
    loop = events.new_event_loop()
    try:
        events.set_event_loop(loop)
        loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
        finally:
            events.set_event_loop(None)
            loop.close()
    
    0 讨论(0)
提交回复
热议问题