【问题标题】:Is it possible to start an asyncio event loop in the background without spawning a thread or process?是否可以在不产生线程或进程的情况下在后台启动异步事件循环?
【发布时间】:2017-09-12 05:44:47
【问题描述】:

我有一个用 Python 3.5 实现的服务,它应该定期通过网络加载文件。我想避免在加载时阻塞主线程。为了避免在我的应用程序中引入线程和共享内存的复杂性,我想使用带有 asyncio 包的事件循环来对此进行检测。

为了运行事件循环,我找到了AbstractEventLoop.run_forever()AbstractEventLoop.run_until_complete(future) 方法,但它们似乎在调用时都阻塞了主线程。我发现避免这种情况的唯一方法是在不同的线程中启动循环。但是如果我仍然使用线程,那么使用事件循环就没有意义了。

所以我的问题是:是否可以在不产生线程或进程的情况下在后台启动异步事件循环?

【问题讨论】:

    标签: python-3.x python-asyncio


    【解决方案1】:

    您可以使用run_in_executor 方法。您使用此方法运行的每个函数都在各自的线程中运行(并行)。

    AbstractEventLoop.run_in_executor() 方法可以与线程池执行器一起使用,在不同的线程中执行回调,不会阻塞事件循环的线程。

    executor 参数应该是一个 Executor 实例。如果 executor 为 None,则使用默认 executor。

    例如:

    import asyncio
    
    def load_file_over_network(file_name):
        # do the file loading
        pass
    
    loop = asyncio.get_event_loop()
    file_name = 'FILE_NAME'
    
    # load the file without blocking
    loop.run_in_executor(None, load_file_over_network, file_name)
    
    # do some other stuff in the main thread
    # while the synchronous code above is running in other threads, the event loop
    # can go do other things
    
    # load the file again without blocking
    loop.run_in_executor(None, load_file_over_network, file_name)
    

    希望这会有所帮助:)

    【讨论】:

    • 嗯...我会说这个答案是错误的。在 python 的文档中,run_in_executor 被标记为协程 - 这意味着应该等待它正常工作。但是,目前它没有作为协程实现,而是返回一个asyncio.Future。即,run_in_executor() 应该在事件循环中运行:尝试从“主线程”获取load_file_over_network() 的返回值。
    • 感谢您指出这一点!所以电话应该是asyncio.ensure_future(loop.run_in_executor(...)) ?
    • 这个答案没有回应这个问题。问题问如何在不阻塞主线程或使用线程的情况下启动事件循环。
    【解决方案2】:

    如果你的整个程序在没有asyncio 的事件循环的情况下运行,那么在新的事件循环中只运行一个任务没有多大意义。相反,请尝试使用简单得多的 concurrent.futures 库。任何调用的任务都会立即返回一个 Future 实例,该实例具有 .done().result() 方法:

    import time
    from concurrent.futures.thread import ThreadPoolExecutor
    
    
    def fetcher():
        print("fetching...")
        time.sleep(2)
        print("got it!")
        return 42
    
    
    with ThreadPoolExecutor(max_workers=10) as executor:
        fut = None
        for i in range(100):
            if i % 10 == 2:
                fut = executor.submit(fetcher)
            print(i, fut)
            if fut and fut.done():
                result = fut.result()  # can raise an error
                print("Done! Got:", result)
                fut = None
            time.sleep(0.5)
    

    【讨论】:

    • 您能否解释一下“在新的事件循环中只运行一项任务没有多大意义。”的基本原理。? (所以即使是不了解asyncio 的人也能理解)
    • 在上面的问题中作者提到他已经有一个没有asyncio(他的“主线程”)编写的程序。使用asyncio,需要修改这个“主线程”才能在不阻塞另一个线程的情况下工作。 concurrent futures 允许您在不修改现有代码的情况下添加另一个线程。
    • 因为我目前正在通过run_in_executor 运行一个单独的线程,而主线程不是。我仍然对为什么这没有意义感到困惑。有什么副作用吗?
    • 这个问题不是专门针对不使用额外线程或进程的解决方案吗?从您的回答看来,您好像在使用额外的线程。
    • @Marc:我的回答根本不使用事件循环,只是线程。
    【解决方案3】:

    嗯,Yuval Pruss's answer 部分正确。实际上,loop.run_in_executor()Executor 参数可以是 ThreadPoolExecutor(默认值)或 ProcessPoolExecutor

    所以答案是否定的,您不能在所有其他代码同步的情况下在后台运行asyncio Task。或者你重写你的应用程序,让它为每个任务使用asyncio,或者你必须使用线程或进程。

    【讨论】:

    • 为什么 ProcessPoolExecutor 在 asyncio 中被弃用?
    • @remort:抱歉,已弃用(现在已删除)将其设置为默认执行程序。我更正了答案。
    猜你喜欢
    • 2016-10-07
    • 2020-12-15
    • 2019-12-02
    • 1970-01-01
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多