Python Trio异步编码模式


本文介绍了使用Python3 Trio创建异步程序时经常使用的模式。

如果您想进一步了解Trio和pytest-trio,建议您使用原始文档。

  • 三重奏:https://trio.readthedocs.io/en/latest/

  • pytest-trio:https://pytest-trio.readthedocs.io/en/latest/

运行环境

  • Python 3.7
  • 三重奏0.11
  • pytest-trio 0.5.2

同时执行异步处理

首先,它是同时执行基本方法和异步方法的一种方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import trio


async def func1():
    await trio.sleep(1)
    print("func1 finished")


async def func2():
    await trio.sleep(1)
    print("func2 finished")


async def main():
    async with trio.open_nursery() as nursery:
        # func1, func2 を同時に実行
        nursery.start_soon(func1)
        nursery.start_soon(func2)
    # 両方のメソッドが完了するのを待つ
    print("all methods finished")


trio.run(main)

执行后,看起来像这样。

1
2
3
func2 finished
func1 finished
all methods finished

您还可以将参数传递给每个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import trio


async def func1(t):
    await trio.sleep(t)
    print("func1 finished")


async def func2(t1, t2):
    await trio.sleep(t1 + t2)
    print("func2 finished")


async def main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(func1, 1.0)
        nursery.start_soon(func2, 0.5, 1.0)
    print("all methods finished")


trio.run(main)

执行结果。

1
2
3
func1 finished
func2 finished
all methods finished

执行多个异步方法并在其中一个完成时中断其他处理

这是一种等待满足多个条件之一的模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import trio


async def cond1(nursery):
    await trio.sleep(1.0)
    print("cond1 satisfied")
    nursery.cancel_scope.cancel()


async def cond2(nursery):
    await trio.sleep(0.5)
    print("cond2 satisfied")
    nursery.cancel_scope.cancel()


async def main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(cond1, nursery)
        nursery.start_soon(cond2, nursery)
    print("one condition satisfied")


trio.run(main)

执行结果。

1
2
cond2 satisfied
one condition satisfied

您可以看到,当

cond2被填充并且cond1没有执行到最后时,该过程结束。

如果创建

包装器方法,则不必将nursery传递给每个方法,因此可以将其写得更整洁(结果相同)。

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
import trio


async def cond1():
    await trio.sleep(1.0)
    print("cond1 satisfied")


async def cond2():
    await trio.sleep(0.5)
    print("cond2 satisfied")


async def wrapper(func, nursery):
    await func()
    nursery.cancel_scope.cancel()


async def main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(wrapper, cond1, nursery)
        nursery.start_soon(wrapper, cond2, nursery)
    print("one condition satisfied")


trio.run(main)

一个过程满足某些条件,然后再启动另一个

用于某些模式,例如等待与数据库的连接以开始读写数据,或等待完成一个计算以执行另一计算。

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
35
36
37
import trio


async def connect_db(*, task_status=trio.TASK_STATUS_IGNORED):
    await trio.sleep(1.0)
    print("connected to DB")
    # nursery にタスクが開始できたことを通知する
    task_status.started()
    try:
        await trio.sleep_forever()
    finally:
        print("disconnected from DB")


async def write_data(data):
    await trio.sleep(0.5)
    print(f"write data to DB: {data}")


async def read_data():
    await trio.sleep(1.0)
    print("read data from DB")
    return "data"


async def main():
    async with trio.open_nursery() as nursery:
        # nursery.start を使うと、タスクが開始された通知が来るまで待つ
        await nursery.start(connect_db)
        # DB への接続を待ってデータの読み書きを開始
        await write_data("foo")
        _ = await read_data()
        nursery.cancel_scope.cancel()
    print("transaction finished")


trio.run(main)

执行结果。

1
2
3
4
5
connected to DB
write data to DB: foo
read data from DB
disconnected from DB
transaction finished

等待与数据库的正确连接,您可以看到write_dataread_data正在运行。

通过以异步方法在try: ~ finally:finally子句中写入终止过程,即使终止过程被nursery.cancel_scope.cancelKeyboardInterrupt中断,也可以执行终止过程。在上面的示例中,在整体完成之前执行了connect_dbfinally

一段时间后取消处理

当您要为异步处理设置超时时,这是一种模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import trio


async def func(name, t):
    try:
        await trio.sleep(t)
        print(f"{name} finished")
    except trio.Cancelled:
        print(f"{name} canceled")


async def main():
    # 2.5秒でタイムアウト
    with trio.move_on_after(2.5):
        async with trio.open_nursery() as nursery:
            nursery.start_soon(func, "func1", 1.0)
            nursery.start_soon(func, "func2", 2.0)
            nursery.start_soon(func, "func3", 3.0)
    print("timeout")


trio.run(main)

执行结果。

1
2
3
4
func1 finished
func2 finished
func3 canceled
timeout

您可以看到在2.5秒内完成的

func1func2已完成,但是需要3秒的func3被中断了。

跳过睡眠时间以减少测试时间

使用

pytest进行测试时,可以使用pytest-trio的autojump_clock功能来减少测试执行时间。

首先,在pytest.ini中将以下内容设置为法术。

pytest.ini

1
2
[pytest]
trio_mode = true

让我们运行以下测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time

import trio


async def test1():
    v0, t0 = trio.current_time(), time.time()
    await trio.sleep(1)
    v1, t1 = trio.current_time(), time.time()
    print(f"test1: virtual time={v1 - v0}sec, real time={t1 - t0}sec")
    assert 1 + 1 == 2


async def test2(autojump_clock):
    v0, t0 = trio.current_time(), time.time()
    await trio.sleep(1)
    v1, t1 = trio.current_time(), time.time()
    print(f"test2: virtual time={v1 - v0}sec, real time={t1 - t0}sec")
    assert 1 + 1 == 2

执行结果。

1
2
test1: virtual time=1.0040155599999707sec, real time=1.0039751529693604sec
test2: virtual time=1.0sec, real time=0.0003070831298828125sec

可以看到,三重奏的经过时间和正常编写的test1的实际执行时间约为1秒。另一方面,在使用autojump_clocktest2中,三重奏的经过时间恰好为1秒,实际执行时间为1毫秒或更短。

如果使用

autojump_clock,则虚拟时钟将在测试中使用,它将跳过未在其他地方处理的trio.sleep的时间,并继续进行下一个处理,因此在测试中为进行测试。通常,如果您在测试中经常使用睡眠,则测试本身会很慢,或者您必须在一段时间内避免睡眠,以免睡眠,但是使用autojump_clock会使编写测试变得容易得多。它将很快。

终于

Python3很好!三重奏很棒!

如果还有其他有用的模式,我会不时更新本文,因此,如果您知道诸如"您有这样的设备吗?"或"有这么有用的功能"之类的信息,请多多关照。可以发表评论。