在自己的线程中运行事件循环

Posted

技术标签:

【中文标题】在自己的线程中运行事件循环【英文标题】:Running an event loop within its own thread 【发布时间】:2017-07-11 15:57:03 【问题描述】:

我正在使用 Python 的新(ish)asyncio 东西,试图将其事件循环与传统线程结合起来。我编写了一个的类,以隔离它,然后提供一个(同步)方法,在该循环上运行协程并返回结果。 (我意识到这使它成为一个毫无意义的例子,因为它必然会序列化所有内容,但它只是作为概念验证)。

import asyncio
import aiohttp
from threading import Thread


class Fetcher(object):
    def __init__(self):
        self._loop = asyncio.new_event_loop()
        # FIXME Do I need this? It works either way...
        #asyncio.set_event_loop(self._loop)

        self._session = aiohttp.ClientSession(loop=self._loop)

        self._thread = Thread(target=self._loop.run_forever)
        self._thread.start()

    def __enter__(self):
        return self

    def __exit__(self, *e):
        self._session.close()
        self._loop.call_soon_threadsafe(self._loop.stop)
        self._thread.join()
        self._loop.close()

    def __call__(self, url:str) -> str:
        # FIXME Can I not get a future from some method of the loop?
        future = asyncio.run_coroutine_threadsafe(self._get_response(url), self._loop)
        return future.result()

    async def _get_response(self, url:str) -> str:
        async with self._session.get(url) as response:
            assert response.status == 200
            return await response.text()


if __name__ == "__main__":
    with Fetcher() as fetcher:
        while True:
            x = input("> ")

            if x.lower() == "exit":
                break

            try:
                print(fetcher(x))
            except Exception as e:
                print(f"WTF? e.__class__.__name__")

为了避免这听起来太像“代码审查”问题,asynchio.set_event_loop 的目的是什么,我在上面需要它吗?无论有没有,它都可以正常工作。此外,是否有循环级方法来调用协程并返回未来?用模块级函数来做这件事似乎有点奇怪。

【问题讨论】:

【参考方案1】:

如果您在任何地方调用get_event_loop 并希望它返回调用new_event_loop 时创建的循环,则需要使用set_event_loop

来自docs

如果需要将此循环设置为当前上下文的事件循环,则必须显式调用set_event_loop()

由于您没有在示例中的任何位置调用 get_event_loop,因此您可以省略对 set_event_loop 的调用。

【讨论】:

这里的“上下文”是什么意思?它不能表示“范围”,因为这样就不需要在任何地方调用set_event_loop,除非在同一范围内有多个循环的奇怪情况。不管缺少get_event_loop,使用set_event_loop 是否是个好主意,以防后者在代码库的其他地方被调用? 它会根据使用的策略而有所不同,但the default policy defines context as the current thread。是的,如果有可能在其他地方使用相同的循环,那么您应该致电set_event_loop 如果默认策略“管理每个线程的事件循环”,这是否意味着我不需要明确调用asyncio.new_event_loop()?也就是说,每个线程是否都有一个隐式事件循环,当它在同一上下文中被调用时,它将由get_event_loop 返回? (显然,在我上面的代码中,我在新线程外部创建了一个新循环,但可以很容易地改变它。) 正确。您可以先致电get_event_loop。仅当您不想要该循环或它已关闭时,您才需要使用 new_event_loop 太棒了:谢谢:)【参考方案2】:

我可能会误解,但我认为@dirn 在标记答案中的评论指出get_event_loop 来自一个线程是不正确的。请参阅以下示例:

import asyncio
import threading

async def hello():
    print('started hello')
    await asyncio.sleep(5)
    print('finished hello')

def threaded_func():
    el = asyncio.get_event_loop()
    el.run_until_complete(hello())

thread = threading.Thread(target=threaded_func)
thread.start()

这会产生以下错误:

RuntimeError: There is no current event loop in thread 'Thread-1'.

可以通过以下方式修复:

 - el = asyncio.get_event_loop()
 + el = asyncio.new_event_loop()

documentation 还指定此技巧(通过调用 get_event_loop 创建事件循环)仅适用于主线程:

如果当前OS线程中没有设置当前事件循环,OS线程是main,并且还没有调用set_event_loop(),asyncio会创建一个新的事件循环并设置为当前。

最后,如果您使用的是 3.7 或更高版本,文档还建议使用 get_running_loop 而不是 get_event_loop

【讨论】:

我很确定他们的意思是,在打电话给get_event_loop() 之前先打电话给set_event_loop(),但是是的get_running_loop() 听起来更好。

以上是关于在自己的线程中运行事件循环的主要内容,如果未能解决你的问题,请参考以下文章

你如何在Tkinter的事件循环中运行自己的代码?

js的事件循环(Eventloop) 机制

js事件循环运行机制

js事件循环运行机制

C++ uWebSockets 在一个线程中集成事件循环

总结javascript基础概念:事件队列循环