关于 python:如何使用 asyncio 并行化阻塞代码

How to parallelise blocking code with asyncio

我知道 StackOverflow 中的 asyncio 功能很多,但尽管这里回答了很多问题,但我仍然不明白如何做一些简单的事情,比如并行化 2 个执行阻塞代码的任务。

例如,这很好用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import asyncio


async def slow_thing():
    await asyncio.sleep(2)


async def try_alpha():
    print("Alpha start")
    await slow_thing()
    print("Alpha stop")
    return"Alpha"


async def try_bravo():
    print("Bravo start")
    await slow_thing()
    print("Bravo stop")
    return"Bravo"


async def main():
    futures = [
        try_alpha(),
        try_bravo(),
    ]
    for response in await asyncio.gather(*futures):
        print(response)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

输出正是我正在寻找的:

1
2
3
4
5
6
7
Alpha start
Bravo start
*2 second wait*
Alpha stop
Bravo stop
Alpha
Bravo

但是,如果我将 await syncio.sleep(2) 换成 time.sleep(2),输出就好像我的代码没有任何异步:

1
2
3
4
5
6
7
8
Alpha start
*2 second wait*
Alpha stop
Bravo start
*2 second wait*
Bravo stop
Alpha
Bravo

问题是,在我的真实示例中,我无法控制那些缓慢的代码,因此我无法将其更改为使用协程。在某些情况下,它只是 requests.get() 的一堆用途,而在其他情况下,我正在使用 kodijson 库,它做了很多我无法访问的事情。

所以我想知道 asyncio 是否是正确的工具。当您尝试与 .gather() 并行化时,是否可以在异步代码中使用阻塞代码?

还请注意,我(不幸地)在这方面坚持使用 Python 3.6。我正在编写一个 Mycroft 扩展程序,而这正是他们目前所坚持的环境。


在我在这里以评论的形式获得帮助后,我能够使用 concurrent.futures:

汇总一个解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import concurrent.futures
import time


def slow_1(s):
    time.sleep(5)
    print(f"1: {s}")
    return"1: ok"


def slow_2(s):
    time.sleep(1)
    print(f"2: {s}")
    return"2: ok"


def slow_3(s):
    time.sleep(1)
    print(f"3: {s}")
    return"3: ok"


with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    futures = (
        executor.submit(slow_1,"x"),
        executor.submit(slow_2,"y"),
        executor.submit(slow_3,"z"),
    )
    concurrent.futures.wait(futures)
    for future in futures:
        try:
            print(future.result())
        except:  # This should obviously be more explicit
            pass

哪些输出:

1
2
3
4
5
6
2: y
3: z
1: x
1: ok
2: ok
3: ok

我应该注意到,官方文档并不清楚你可以通过在未来调用 .result() 从函数中获取返回值,或者你需要遍历 futures 值来获取说结果。 .wait() 按返回顺序返回 donenot_done 值的元组,因此循环遍历 done 的值对我来说破坏了很多事情。如果你像我一样,只想一次做 3 件缓慢的事情并从这三件事情中得到结果,那么这段代码可能适合你。


只有当有东西等待时,协程才能"并行"做一些事情。例如,在您上面的代码中,它与 asyncio.sleep 一起工作的原因是您可以在其上等待它。您只能等待为此目的而设计的特定功能。这就是标准 time.sleep 不起作用的原因,因为您不能使用关键字 await 。 requests 库也是如此。

幸运的是,您可以使用美妙的 aiohttp 库:https://docs.aiohttp.org
这将为您提供同时发出多个请求所需的确切信息。