【问题标题】:Run blocking and unblocking tasks together with asyncio与 asyncio 一起运行阻塞和解除阻塞任务
【发布时间】:2020-12-21 13:00:03
【问题描述】:

我想同时异步运行阻塞和解除阻塞任务。显然,有必要使用 run_in_executor 方法来阻止来自 asyncio 的任务。这是我的示例代码:

import asyncio
import concurrent.futures
import datetime
import time


def blocking():
    print("Enter to blocking()", datetime.datetime.now().time())
    time.sleep(2)
    print("Exited from blocking()", datetime.datetime.now().time())


async def waiter():
    print("Enter to waiter()", datetime.datetime.now().time())
    await asyncio.sleep(3)
    print("Exit from waiter()", datetime.datetime.now().time())


async def asynchronous(loop):
    print("Create tasks", datetime.datetime.now().time())
    task_1 = asyncio.create_task(waiter())

    executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
    task_2 = loop.run_in_executor(executor, blocking)

    tasks = [task_1, task_2]
    print("Tasks are created", datetime.datetime.now().time())
    await asyncio.wait(tasks)


if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.run_until_complete(asynchronous(loop))
    except (OSError) as exc:
        sys.exit('Exception: ' + str(exc))

我应该使用相同的事件循环来阻塞run_in_executor中的任务,还是必须使用另一个?我应该在我的代码中进行哪些更改以使其异步工作?谢谢

【问题讨论】:

  • 除了答案所说的之外,请注意您不需要为每次运行创建新的线程池。线程池的重点是重用线程以提高效率。您可以简单地将None 作为第一个参数传递给run_in_executor,并让它使用由 asyncio 创建的线程池进行事件循环。
  • 哦,我明白了。但是如果我不使用自定义线程拉取的单独线程,它会阻塞带有事件循环的线程,不是吗?
  • 我不是说你应该使用线程池,我只是说你不需要创建自己的线程池。将 None 传递给 run_in_executor 将使用 asyncio 自己的线程池,该线程池就是为此目的而存在的。该线程池仍然有自己的线程,不会阻塞事件池线程。
  • 我明白了,现在。我只是对官方文档中的这句话感到困惑:“loop.run_in_executor() 方法可以与 concurrent.futures.ThreadPoolExecutor 一起使用,以在不同的 OS 线程中执行阻塞代码,而不会阻塞运行事件循环的 OS 线程。 "我认为默认情况下只有一个带有事件循环的线程,使用您自己的线程池可以避免带有事件循环的主线程阻塞
  • 你想的没错,确实只有一个线程在运行事件循环。这个线程池是为你这种情况提供的一个帮手的东西,避免每个人都创建自己的线程池和线程数无限制的增长。所以你必须使用 a 线程池是真的,但它不一定是你自己的 - asyncio 提供的 thread pool (与线程不同)运行事件循环)非常好。

标签: python async-await python-asyncio


【解决方案1】:

必须使用相同的循环。循环委托给执行程序,执行程序运行任务是事件循环的独立线程。所以你不必担心你的阻塞任务会阻塞事件循环。如果您使用单独的循环,则事件循环中的异步函数将无法等待阻塞在新循环中运行的函数的结果。

事件循环通过创建代表执行者任务的未来来管理这一点。然后它在其中一个执行器线程中运行阻塞任务,并在执行器任务返回时设置未来的结果并将控制权返回给事件循环中的等待函数(如果有)。

【讨论】:

  • 非常感谢。那么上面的代码似乎是正确的?创建了一个事件循环,用于阻塞和非阻塞任务。
  • 这很好,除了你应该避免在异步函数中传递事件循环——它可能会导致错误。相反,如果异步函数需要访问事件循环,请使用 asyncio.get_running_loop()。这是 3.7 中的新功能,因此如果您使用 3.6 或更低版本,请使用 get_event_loop()
猜你喜欢
  • 2022-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
  • 1970-01-01
  • 1970-01-01
  • 2022-10-23
相关资源
最近更新 更多