Hướng dẫn python-trio - python-bộ ba

Chào mừng bạn đến với hướng dẫn bộ ba! Trio là một thư viện Python hiện đại để viết các ứng dụng không đồng bộ - nghĩa là các chương trình muốn làm nhiều việc cùng một lúc với I/O song song, giống như một con nhện web có được nhiều trang song song, một máy chủ web đang tung hứng rất nhiều Tải xuống loại đó. Ở đây, chúng tôi sẽ cố gắng giới thiệu nhẹ nhàng về lập trình không đồng bộ với bộ ba.

Chúng tôi cho rằng bạn đã quen thuộc với Python nói chung, nhưng đừng lo lắng - chúng tôi không cho rằng bạn biết bất cứ điều gì về chương trình không đồng bộ hoặc tính năng

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
5 mới của Python.

Ngoài ra, không giống như nhiều hướng dẫn

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
5, chúng tôi cho rằng mục tiêu của bạn là sử dụng bộ ba để viết các chương trình thú vị, vì vậy chúng tôi đã giành chiến thắng trong các chi tiết nitty-gritty về cách
import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
5 được triển khai bên trong phiên dịch Python. Từ Cor Coroutine không bao giờ được đề cập. Thực tế là, bạn thực sự không cần phải biết bất kỳ thứ gì trong số đó trừ khi bạn muốn thực hiện một thư viện như Trio, vì vậy chúng tôi bỏ nó ra (mặc dù chúng tôi sẽ ném vào một vài liên kết cho những người muốn đào sâu hơn).

Được rồi, đã sẵn sàng? Bắt đầu nào.

Trước khi bắt đầu¶

  1. Hãy chắc chắn rằng bạn đã sử dụng Python 3.7 hoặc mới hơn.

  2. import trio
    
    async def double_sleep(x):
        await trio.sleep(2 * x)
    
    trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
    
    8 (hoặc trên Windows, có thể
    import trio
    
    async def double_sleep(x):
        await trio.sleep(2 * x)
    
    trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
    
    9 - chi tiết)

  3. Bạn có thể

    trio.run -> double_sleep -> trio.sleep
    
    0? Nếu vậy thì bạn rất tốt để đi!

Nếu bạn bị lạc hoặc bối rối… ¶

Sau đó, chúng tôi muốn biết! Chúng tôi có một kênh trò chuyện thân thiện, bạn có thể đặt câu hỏi bằng cách sử dụng thẻ python-trio, trên stackoverflow hoặc chỉ gửi lỗi (nếu tài liệu của chúng tôi khó hiểu, đó là lỗi của chúng tôi và chúng tôi muốn sửa nó!).

Chức năng không đồng bộ

Python 3.5 đã thêm một tính năng mới: các chức năng Async. Sử dụng bộ ba là tất cả về việc viết các chức năng Async, vì vậy hãy để bắt đầu từ đó.

Hàm async được xác định như hàm bình thường, ngoại trừ bạn viết

trio.run -> double_sleep -> trio.sleep
1 thay vì
trio.run -> double_sleep -> trio.sleep
2:

# A regular function
def regular_double(x):
    return 2 * x

# An async function
async def async_double(x):
    return 2 * x

Voi Async là viết tắt của người Viking không đồng bộ; Đôi khi, chúng tôi đề cập đến các chức năng thường xuyên như

trio.run -> double_sleep -> trio.sleep
3 là các hàm đồng bộ của Hồi giáo, để phân biệt chúng với các hàm async.

Từ quan điểm của người dùng, có hai sự khác biệt giữa hàm async và chức năng chính quy:

  1. Để gọi hàm Async, bạn phải sử dụng từ khóa

    trio.run -> double_sleep -> trio.sleep
    
    4. Vì vậy, thay vì viết
    trio.run -> double_sleep -> trio.sleep
    
    5, bạn viết
    trio.run -> double_sleep -> trio.sleep
    
    6.

  2. Bạn có thể sử dụng từ khóa

    trio.run -> double_sleep -> trio.sleep
    
    4 bên trong phần thân của chức năng thông thường. Nếu bạn thử nó, bạn sẽ gặp lỗi cú pháp:

    def print_double(x):
        print(await async_double(x))   # <-- SyntaxError here
    

    Nhưng bên trong một hàm Async,

    trio.run -> double_sleep -> trio.sleep
    
    4 được cho phép:

    async def print_double(x):
        print(await async_double(x))   # <-- OK!
    

Bây giờ, hãy để Lừa nghĩ về hậu quả ở đây: Nếu bạn cần

trio.run -> double_sleep -> trio.sleep
4 để gọi hàm Async và chỉ các hàm Async mới có thể sử dụng ____ 54 ở đây, một bảng nhỏ:

Nếu một chức năng như thế này

muốn gọi một chức năng như thế này

Nó sẽ xảy ra?

đồng bộ hóa

đồng bộ hóa

đồng bộ hóa

không đồng bộ

đồng bộ hóa

không đồng bộ

KHÔNG

Vì vậy, Tóm lại: với tư cách là người dùng, toàn bộ lợi thế của các hàm Async so với các chức năng thông thường là các chức năng Async có siêu năng lực: họ có thể gọi các chức năng Async khác.

Điều này ngay lập tức đặt ra hai câu hỏi: làm thế nào, và tại sao? Đặc biệt:

Khi chương trình Python của bạn bắt đầu, nó chạy mã đồng bộ hóa cũ thông thường. Vì vậy, có một vấn đề về gà và trứng: một khi chúng ta đang chạy một chức năng Async, chúng ta có thể gọi các chức năng Async khác, nhưng làm thế nào để chúng ta gọi hàm Async đầu tiên đó?

  1. Và, nếu lý do duy nhất để viết một hàm Async là nó có thể gọi các chức năng Async khác, tại sao chúng ta lại sử dụng chúng ngay từ đầu? Ý tôi là, khi siêu năng lực đi, điều này có vẻ hơi vô nghĩa. Sẽ đơn giản hơn khi chỉ cần sử dụng bất kỳ chức năng ASYNC nào?

    import trio
    
    async def async_double(x):
        return 2 * x
    
    trio.run(async_double, 3)  # returns 6
    

    Đây là nơi một thư viện Async như Trio xuất hiện. Nó cung cấp hai điều:

  2. Một hàm chạy, là một hàm đồng bộ đặc biệt lấy và gọi một hàm không đồng bộ. Trong bộ ba, đây là

    trio.run -> [async function] -> ... -> [async function] -> trio.whatever
    
    1:

    Vì vậy, câu trả lời cho các phần của người Viking.

    import trio
    
    async def double_sleep(x):
        await trio.sleep(2 * x)
    
    trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
    

Một loạt các chức năng Async hữu ích - đặc biệt là các chức năng để thực hiện I/O. Vì vậy, câu trả lời cho các vấn đề tại sao: các chức năng này là async và chúng rất hữu ích, vì vậy nếu bạn muốn sử dụng chúng, bạn phải viết mã async. Nếu bạn nghĩ rằng việc theo dõi những

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
2 và
trio.run -> double_sleep -> trio.sleep
4 này thật khó chịu, thì quá tệ - bạn đã không có lựa chọn nào khác trong vấn đề này! .

trio.run -> double_sleep -> trio.sleep

This “sandwich” structure is typical for async code; in general, it looks like:

trio.run -> [async function] -> ... -> [async function] -> trio.whatever

It’s exactly the functions on the path between

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 and
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
1 that have to be async. Trio provides the async bread, and then your code makes up the async sandwich’s tasty async filling. Other functions (e.g., helpers you call along the way) should generally be regular, non-async functions.

Warning: don’t forget that trio.run -> double_sleep -> trio.sleep 4!¶

Now would be a good time to open up a Python prompt and experiment a little with writing simple async functions and running them with

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
1.

At some point in this process, you’ll probably write some code like this, that tries to call an async function but leaves out the

trio.run -> double_sleep -> trio.sleep
4:

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)

You might think that Python would raise an error here, like it does for other kinds of mistakes we sometimes make when calling a function. Like, if we forgot to pass

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5 its required argument, then we would get a nice
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
6 saying so. But unfortunately, if you forget an
trio.run -> double_sleep -> trio.sleep
4, you don’t get that. What you actually get is:

>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
__main__:4: RuntimeWarning: coroutine 'sleep' was never awaited
>>>

This is clearly broken – 0.00 seconds is not long enough to feel well rested! Yet the code acts like it succeeded – no exception was raised. The only clue that something went wrong is that it prints

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
8. Also, the exact place where the warning is printed might vary, because it depends on the whims of the garbage collector. If you’re using PyPy, you might not even get a warning at all until the next GC collection runs:

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>

(If you can’t see the warning above, try scrolling right.)

Forgetting an

trio.run -> double_sleep -> trio.sleep
4 like this is an incredibly common mistake. You will mess this up. Everyone does. And Python will not help you as much as you’d hope 😞. The key thing to remember is: if you see the magic words
>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
__main__:4: RuntimeWarning: coroutine 'sleep' was never awaited
>>>
0, then this always means that you made the mistake of leaving out an
trio.run -> double_sleep -> trio.sleep
4 somewhere, and you should ignore all the other error messages you see and go fix that first, because there’s a good chance the other stuff is just collateral damage. I’m not even sure what all that other junk in the PyPy output is. Fortunately I don’t need to know, I just need to fix my function!

(“I thought you said you weren’t going to mention coroutines!” Yes, well, I didn’t mention coroutines, Python did. Take it up with Guido! But seriously, this is unfortunately a place where the internal implementation details do leak out a bit.)

Why does this happen? In Trio, every time we use

trio.run -> double_sleep -> trio.sleep
4 it’s to call an async function, and every time we call an async function we use
trio.run -> double_sleep -> trio.sleep
4. But Python’s trying to keep its options open for other libraries that are ahem a little less organized about things. So while for our purposes we can think of
>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
__main__:4: RuntimeWarning: coroutine 'sleep' was never awaited
>>>
4 as a single piece of syntax, Python thinks of it as two things: first a function call that returns this weird “coroutine” object:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
0

and then that object gets passed to

trio.run -> double_sleep -> trio.sleep
4, which actually runs the function. So if you forget
trio.run -> double_sleep -> trio.sleep
4, then two bad things happen: your function doesn’t actually get called, and you get a “coroutine” object where you might have been expecting something else, like a number:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
1

If you didn’t already mess this up naturally, then give it a try on purpose: try writing some code with a missing

trio.run -> double_sleep -> trio.sleep
4, or an extra
trio.run -> double_sleep -> trio.sleep
4, and see what you get. This way you’ll be prepared for when it happens to you for real.

And remember: watch out for

>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
__main__:4: RuntimeWarning: coroutine 'sleep' was never awaited
>>>
9; it means you need to find and fix your missing
trio.run -> double_sleep -> trio.sleep
4.

Okay, let’s see something cool already¶

So now we’ve started using Trio, but so far all we’ve learned to do is write functions that print things and sleep for various lengths of time. Interesting enough, but we could just as easily have done that with

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
7.
import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
5 is useless!

Well, not really. Trio has one more trick up its sleeve, that makes async functions more powerful than regular functions: it can run multiple async functions at the same time. Here’s an example:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
2

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
3

There’s a lot going on in here, so we’ll take it one step at a time. In the first part, we define two async functions

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 and
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4. These should look familiar from the last section:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
4

Next, we define

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 as an async function that’s going to call
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 and
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4 concurrently:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
5

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
6

It does this by using a mysterious

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 statement to create a “nursery”, and then “spawns”
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 and
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4 into the nursery.

Let’s start with this

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 thing. It’s actually pretty simple. In regular Python, a statement like
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
02 instructs the interpreter to call
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
03 at the beginning of the block, and to call
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
04 at the end of the block. We call
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
05 a “context manager”. An
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
06 does exactly the same thing, except that where a regular
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
07 statement calls regular methods, an
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 statement calls async methods: at the start of the block it does
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
09 and at that end of the block it does
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
10. In this case we call
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
05 an “async context manager”. So in short:
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
07 blocks are a shorthand for calling some functions, and since with async/await Python now has two kinds of functions, it also needs two kinds of
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
07 blocks. That’s all there is to it! If you understand async functions, then you understand
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8.

Ghi chú

Ví dụ này không sử dụng chúng, nhưng trong khi chúng tôi ở đây, chúng tôi cũng có thể đề cập đến một phần cú pháp mới khác mà Async/đang chờ thêm vào:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
15. Nó về cơ bản là ý tưởng tương tự như
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
06 so với
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
07: vòng lặp
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
15 giống như một vòng lặp
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
19, ngoại trừ khi vòng lặp
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
19 không
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
21 để lấy vật phẩm tiếp theo, một
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
15 không
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
23. Bây giờ bạn đã hiểu tất cả các async/đang chờ đợi. Về cơ bản, chỉ cần nhớ rằng nó liên quan đến việc làm bánh sandwich và dán từ ngữ async, trước tất cả mọi thứ, và bạn sẽ làm tốt.

Bây giờ chúng tôi đã hiểu

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8, hãy để Lừa nhìn vào
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 một lần nữa:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
5

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
6

Chỉ có 4 dòng mã thực sự làm bất cứ điều gì ở đây. Trên dòng 17, chúng tôi sử dụng

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
26 để có được một đối tượng của nhà trẻ, và sau đó bên trong khối
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 mà chúng tôi gọi là
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
28 hai lần, trên các dòng 19 và 22. Thực tế có hai cách để gọi một hàm Async: đã thấy, sử dụng
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
29; Cái mới là
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
30: nó yêu cầu bộ ba bắt đầu chạy chức năng Async này, nhưng sau đó trả về ngay lập tức mà không chờ đợi chức năng kết thúc. Vì vậy, sau khi hai cuộc gọi của chúng tôi đến
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
28,
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 và
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4 hiện đang chạy trong nền. Và sau đó ở dòng 25, dòng nhận xét, chúng tôi đã đạt đến cuối khối ____998 và chức năng
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
35 của Vườn ươm chạy. Những gì điều này làm là buộc ____995 dừng lại ở đây và chờ đợi tất cả trẻ em trong vườn ươm thoát ra. Đây là lý do tại sao bạn phải sử dụng
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 để có được một vườn ươm: nó cho chúng tôi một cách để đảm bảo rằng đứa trẻ gọi có thể chạy trốn và bị lạc. Một lý do điều này rất quan trọng là nếu có một lỗi hoặc vấn đề khác ở một trong những đứa trẻ, và nó sẽ đặt ra một ngoại lệ, thì nó cho phép chúng ta tuyên truyền ngoại lệ đó vào cha mẹ; Trong nhiều khung khác, các trường hợp ngoại lệ như thế này chỉ bị loại bỏ. Bộ ba không bao giờ loại bỏ ngoại lệ.

Được! Hãy để thử chạy nó và xem những gì chúng ta nhận được:

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
9

.

Lưu ý rằng cả

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 và
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4 đều bắt đầu cùng nhau và sau đó cả hai thoát ra cùng nhau. Và, mặc dù chúng tôi đã thực hiện hai cuộc gọi đến
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
40, chương trình đã hoàn thành chỉ trong tổng cộng một giây. Vì vậy, có vẻ như
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 và
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4 thực sự đang chạy cùng một lúc!

Bây giờ, nếu bạn quen thuộc với lập trình bằng cách sử dụng các chủ đề, điều này có thể trông quen thuộc - và đó là cố ý. Nhưng điều quan trọng là phải nhận ra rằng không có chủ đề ở đây. Tất cả điều này đang xảy ra trong một chủ đề duy nhất. Để nhắc nhở bản thân về điều này, chúng tôi sử dụng thuật ngữ hơi khác nhau: thay vì sinh sản hai chủ đề của người Hồi giáo, chúng tôi nói rằng chúng tôi đã sinh ra hai nhiệm vụ của Hồi giáo. Có hai sự khác biệt giữa các tác vụ và luồng: (1) Nhiều tác vụ có thể thay phiên nhau chạy trên một luồng và (2) với các luồng, trình thông dịch Python/hệ điều hành có thể chuyển đổi luồng nào đang chạy bất cứ khi nào chúng cảm thấy thích nó; Với các nhiệm vụ, chúng tôi chỉ có thể chuyển đổi ở một số địa điểm được chỉ định nhất định mà chúng tôi gọi là điểm kiểm tra. Trong phần tiếp theo, chúng tôi sẽ đào sâu vào ý nghĩa của điều này.“checkpoints”. In the next section, we’ll dig into what this means.

Chuyển đổi nhiệm vụ minh họa Or

Ý tưởng lớn đằng sau các thư viện dựa trên Async/đang chờ đợi như Trio là chạy đồng thời nhiều tác vụ trên một luồng bằng cách chuyển đổi giữa chúng tại các địa điểm thích hợp-ví dụ, nếu chúng ta đang triển khai một máy chủ web, thì một tác vụ có thể gửi Phản hồi HTTP cùng lúc với một nhiệm vụ khác đang chờ các kết nối mới. Nếu tất cả những gì bạn muốn làm là sử dụng bộ ba, thì bạn không cần phải hiểu tất cả các chi tiết nitty-gritty về cách chuyển đổi này hoạt động-nhưng rất hữu ích khi có ít nhất một trực giác chung về những gì bộ ba đang làm Khi mã của bạn được thực thi. Để giúp xây dựng trực giác đó, hãy để Lôi xem xét kỹ hơn về cách Trio chạy ví dụ của chúng tôi từ phần cuối cùng.

May mắn thay, bộ ba cung cấp một bộ công cụ phong phú để kiểm tra và gỡ lỗi các chương trình của bạn. Ở đây chúng tôi muốn xem

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 tại nơi làm việc, mà chúng tôi có thể làm bằng cách viết một lớp mà chúng tôi sẽ gọi là
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
44, trong đó thực hiện giao diện Trio Trio ____145. Công việc của nó là đăng nhập các sự kiện khác nhau khi chúng xảy ra:rich set of tools for inspecting and debugging your programs. Here we want to watch
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 at work, which we can do by writing a class we’ll call
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
44, which implements Trio’s
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
45 interface. Its job is to log various events as they happen:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
0

Sau đó, chúng tôi chạy lại chương trình ví dụ của mình từ phần trước, nhưng lần này chúng tôi vượt qua

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 một đối tượng
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
44:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
1

Điều này tạo ra rất nhiều đầu ra, vì vậy chúng tôi sẽ trải qua từng bước một.

Đầu tiên, có một chút trò chuyện trong khi bộ ba đã sẵn sàng để chạy mã của chúng tôi. Hiện tại, hầu hết điều này không liên quan đến chúng tôi, nhưng ở giữa, bạn có thể thấy rằng bộ ba đã tạo ra một nhiệm vụ cho chức năng

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
48, và theo lịch trình của nó (tức là, ghi chú rằng nó sẽ sớm được chạy):

async def print_double(x):
    print(await async_double(x))   # <-- OK!
2

Khi công việc dọn phòng ban đầu được thực hiện, bộ ba bắt đầu chạy chức năng

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 và bạn có thể thấy
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 tạo ra hai nhiệm vụ trẻ em. Sau đó, nó đánh vào cuối khối
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 và tạm dừng:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
3

Kiểm soát sau đó quay trở lại

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0, ghi lại một chút trò chuyện bên trong:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
4

Và sau đó cho hai nhiệm vụ trẻ em một cơ hội để chạy:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
5

Mỗi nhiệm vụ chạy cho đến khi nó đạt được cuộc gọi đến

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5, và rồi đột nhiên chúng tôi quay lại trong
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 quyết định những gì sẽ chạy tiếp theo. Làm thế nào điều này xảy ra? Bí quyết là
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 và
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5 hợp tác để thực hiện nó:
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5 có quyền truy cập vào một số phép thuật đặc biệt cho phép nó tự tạm dừng, do đó, nó gửi một ghi chú đến
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 yêu cầu đánh thức lại sau 1 giây, sau đó đình chỉ nhiệm vụ. Và một khi nhiệm vụ bị đình chỉ, Python sẽ kiểm soát trở lại
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0, quyết định phải làm gì tiếp theo. .

Ghi chú

Bạn có thể tự hỏi liệu bạn có thể kết hợp các nguyên thủy từ các thư viện Async khác nhau hay không. Ví dụ, chúng ta có thể sử dụng

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 cùng với
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
62 không? Câu trả lời là không, chúng ta có thể, và đoạn trên giải thích lý do tại sao: hai mặt của bánh sandwich async của chúng ta có ngôn ngữ riêng mà họ sử dụng để nói chuyện với nhau và các thư viện khác nhau sử dụng các ngôn ngữ khác nhau. Vì vậy, nếu bạn cố gắng gọi
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
62 từ bên trong
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0, thì Trio sẽ thực sự rất bối rối và có lẽ nổ tung theo một cách kịch tính nào đó.

Chỉ các chức năng Async mới có quyền truy cập vào phép thuật đặc biệt để đình chỉ một tác vụ, vì vậy chỉ các hàm Async mới có thể khiến chương trình chuyển sang một nhiệm vụ khác. Điều này có nghĩa là nếu một cuộc gọi không có

trio.run -> double_sleep -> trio.sleep
4 trên đó, thì bạn biết rằng nó có thể là nơi mà nhiệm vụ của bạn sẽ bị đình chỉ. Điều này làm cho các nhiệm vụ dễ lý luận hơn nhiều so với các chủ đề, bởi vì có rất nhiều cách mà các nhiệm vụ có thể được xen kẽ với nhau và dẫm lên trạng thái của nhau. .further guarantees beyond that, but that’s the big one.

Và bây giờ bạn cũng biết lý do tại sao

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 phải sử dụng
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 để mở vườn ươm: Nếu chúng tôi đã sử dụng một khối
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
07 thông thường, thì nó sẽ không thể tạm dừng ở cuối và chờ trẻ em kết thúc; Chúng ta cần chức năng dọn dẹp của chúng ta là async, đó chính xác là những gì
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 mang lại cho chúng ta.

Bây giờ, trở lại điểm thực thi của chúng tôi. Tóm lại: Tại thời điểm này

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 đang chờ đợi trên
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
3 và
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
4, và cả hai đứa trẻ đang ngủ. Vì vậy,
import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 kiểm tra các ghi chú của nó và thấy rằng không có gì phải làm cho đến khi những giấc ngủ đó kết thúc - trừ khi có thể một số sự kiện I/O bên ngoài xuất hiện. Nếu điều đó xảy ra, thì nó có thể cho chúng ta điều gì đó để làm. Tất nhiên, chúng tôi đã thực hiện bất kỳ I/O nào ở đây để nó giành chiến thắng, nhưng trong các tình huống khác, nó có thể. Vì vậy, tiếp theo, nó gọi một hệ điều hành nguyên thủy để đưa toàn bộ quá trình vào giấc ngủ:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
6

Và trên thực tế, không có I/O nào đến, vì vậy một giây sau chúng tôi lại thức dậy và bộ ba kiểm tra lại ghi chú của nó. Tại thời điểm này, nó kiểm tra thời gian hiện tại, so sánh nó với các ghi chú mà

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5 đã gửi nói khi nào hai nhiệm vụ trẻ em nên được đánh thức một lần nữa, và nhận ra rằng họ đã ngủ đủ lâu, vì vậy nó lên lịch cho họ sớm chạy:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
7

Và sau đó bọn trẻ được chạy, và lần này chúng chạy đến hoàn thành. Hãy nhớ làm thế nào

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 đang chờ họ kết thúc? Lưu ý cách
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 được lên lịch khi đứa con đầu tiên thoát ra:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
8

Sau đó, sau khi một kiểm tra khác cho I/O,

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 thức dậy. Mã dọn dẹp vườn ươm thông báo rằng tất cả trẻ em của nó đã thoát ra, và cho phép khối nhà trẻ kết thúc. Và sau đó
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
5 tạo ra một bản in cuối cùng và thoát:

async def print_double(x):
    print(await async_double(x))   # <-- OK!
9

Và cuối cùng, sau một chút sổ sách kế toán nội bộ,

import time
import trio

async def broken_double_sleep(x):
    print("*yawn* Going to sleep")
    start_time = time.perf_counter()

    # Whoops, we forgot the 'await'!
    trio.sleep(2 * x)

    sleep_time = time.perf_counter() - start_time
    print(f"Woke up after {sleep_time:.2f} seconds, feeling well rested!")

trio.run(broken_double_sleep, 3)
0 cũng thoát:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
0

Bạn đã thực hiện nó!

Đó là rất nhiều văn bản, nhưng một lần nữa, bạn không cần phải hiểu mọi thứ ở đây để sử dụng bộ ba - trên thực tế, bộ ba có thời gian dài để làm cho mỗi nhiệm vụ cảm thấy như nó thực hiện một cách đơn giản, tuyến tính. . Nhưng nó rất hữu ích khi có một mô hình thô trong đầu của bạn về cách mã bạn viết thực sự được thực thi, và - quan trọng nhất - hậu quả của điều đó đối với sự song song.

Ngoài ra, nếu điều này vừa mới bắt đầu cảm giác thèm ăn và bạn muốn biết thêm về cách

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
5 hoạt động nội bộ, thì bài đăng trên blog này là một chuyến lặn sâu hoặc xem hướng dẫn tuyệt vời này để xem cách xây dựng khung I/O Async đơn giản từ mặt đất lên.

Một người tử tế hơn, nhẹ nhàng hơn Gil¶

Nói về sự song song - hãy để phóng to ra một lúc và nói về cách Async/Await so sánh với các cách xử lý đồng thời khác trong Python.

Như chúng tôi đã lưu ý, các tác vụ của bộ ba về mặt khái niệm khá giống với các chủ đề tích hợp của Python, như được cung cấp bởi mô-đun

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
83. Và trong tất cả các triển khai Python phổ biến, các chủ đề có một giới hạn nổi tiếng: Khóa phiên dịch toàn cầu, hay viết tắt là Gil Gil. Gil có nghĩa là ngay cả khi bạn sử dụng nhiều luồng, mã của bạn vẫn (chủ yếu) kết thúc chạy trên một lõi. Mọi người có xu hướng tìm thấy điều này bực bội.

Nhưng theo quan điểm của bộ ba, vấn đề với Gil isn mà nó hạn chế sự song song. Tất nhiên sẽ rất tuyệt nếu Python có các lựa chọn tốt hơn để tận dụng nhiều lõi, nhưng đó là một vấn đề cực kỳ khó khăn để giải quyết, và trong khi đó, có rất nhiều vấn đề trong đó một cốt lõi hoàn toàn đầy đủ - hoặc nếu nó không phải là ' T, sau đó song song cấp độ xử lý hoặc cấp độ máy hoạt động tốt.

Không, vấn đề với Gil là đó là một thỏa thuận tệ hại: Chúng tôi từ bỏ việc sử dụng nhiều lõi, và để đổi lấy chúng tôi nhận được hầu hết các thử thách và lỗi uốn cong tâm trí đi kèm với lập trình song song thực sự, và-để thêm sự xúc phạm gây thương tích - Khả năng mở rộng khá kém. Chủ đề trong Python chỉ là aren hấp dẫn.

Bộ ba không làm cho mã của bạn chạy trên nhiều lõi; Trên thực tế, như chúng ta đã thấy ở trên, nó đã nướng vào thiết kế của bộ ba rằng khi nó có nhiều nhiệm vụ, họ thay phiên nhau, vì vậy tại mỗi thời điểm, chỉ có một trong số chúng đang tích cực chạy. Chúng tôi không quá nhiều vượt qua Gil khi nắm lấy nó. Nhưng nếu bạn sẵn sàng chấp nhận điều đó, cộng với một chút công việc để đặt các từ khóa

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
2 và
trio.run -> double_sleep -> trio.sleep
4 mới này vào đúng nơi, thì để đổi lại bạn nhận được:

  • Khả năng mở rộng tuyệt vời: Bộ ba có thể chạy hơn 10.000 nhiệm vụ đồng thời mà không đổ mồ hôi, miễn là tổng nhu cầu CPU của họ không vượt quá những gì một lõi duy nhất có thể cung cấp. .

  • Các tính năng ưa thích: Hầu hết các hệ thống luồng được triển khai trong C và giới hạn ở bất kỳ tính năng nào mà hệ điều hành cung cấp. Trong bộ ba logic của chúng tôi là tất cả trong Python, điều này có thể thực hiện các tính năng mạnh mẽ và công thái học như hệ thống hủy bỏ bộ ba.Trio’s cancellation system.

  • Mã mà dễ dàng hơn để lý luận về: Từ khóa

    trio.run -> double_sleep -> trio.sleep
    
    4 có nghĩa là các điểm chuyển đổi nhiệm vụ tiềm năng được đánh dấu rõ ràng trong mỗi hàm. Điều này có thể làm cho mã Trio dễ dàng hơn đáng kể so với chương trình tương đương bằng cách sử dụng các luồng.

Chắc chắn nó không phù hợp với mọi ứng dụng, nhưng có rất nhiều tình huống mà sự đánh đổi ở đây trông khá hấp dẫn.

Mặc dù vậy, có một nhược điểm mà rất quan trọng để ghi nhớ. Làm cho các điểm kiểm tra rõ ràng cho bạn quyền kiểm soát nhiều hơn về cách các nhiệm vụ của bạn có thể được xen kẽ - nhưng với sức mạnh lớn là trách nhiệm lớn. Với các chủ đề, môi trường thời gian chạy chịu trách nhiệm đảm bảo rằng mỗi luồng đều có phần thời gian chạy công bằng. Với Trio, nếu một số tác vụ hết và thực hiện công cụ trong vài giây mà không cần thực hiện điểm kiểm tra, thì tất cả các nhiệm vụ khác của bạn sẽ phải chờ.

Ở đây, một ví dụ về cách điều này có thể đi sai. Lấy ví dụ của chúng tôi từ trên cao và thay thế các cuộc gọi đến

trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5 bằng các cuộc gọi đến
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
7. Nếu chúng tôi chạy chương trình sửa đổi của mình, chúng tôi sẽ thấy một cái gì đó như:example from above, and replace the calls to
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5 with calls to
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
7. If we run our modified program, we’ll see something like:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
1

Một trong những lý do chính khiến Trio có API thiết bị phong phú như vậy là để có thể viết các công cụ gỡ lỗi để nắm bắt các vấn đề như thế này.instrumentation API is to make it possible to write debugging tools to catch issues like this.

Kết nối với bộ ba

Bây giờ, hãy để Lừa lấy những gì chúng tôi đã học và sử dụng nó để thực hiện một số I/O, đó là nơi mà Async/chờ đợi thực sự tỏa sáng.

Ứng dụng đồ chơi truyền thống để trình diễn API mạng là một máy chủ Echo Echo: một chương trình chờ dữ liệu tùy ý từ các máy khách từ xa, sau đó gửi lại dữ liệu đó ngay lập tức. .

Trong hướng dẫn này, chúng tôi trình bày cả hai đầu của đường ống: máy khách và máy chủ. Máy khách định kỳ gửi dữ liệu đến máy chủ và hiển thị câu trả lời của nó. Máy chủ đang chờ kết nối; Khi một khách hàng kết nối, nó sẽ tái sử dụng dữ liệu nhận được trên đường ống.

Một khách hàng Echo

Để bắt đầu, ở đây, một ví dụ Echo Client, tức là, chương trình sẽ gửi một số dữ liệu tại máy chủ Echo của chúng tôi và nhận lại phản hồi:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
2

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
3

Lưu ý rằng mã này sẽ không hoạt động mà không có máy chủ TCP, chẳng hạn như mã chúng tôi sẽ triển khai dưới đây.

Cấu trúc tổng thể ở đây phải quen thuộc, bởi vì nó giống như ví dụ cuối cùng của chúng tôi: chúng tôi có một nhiệm vụ cha mẹ, điều này sinh ra hai nhiệm vụ con để thực hiện công việc thực tế, và sau đó vào cuối

# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8, nó chuyển sang chế độ nuôi dạy con toàn thời gian Trong khi chờ đợi họ kết thúc. Nhưng bây giờ thay vì chỉ gọi
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5, trẻ em sử dụng một số API mạng Trio Trio.last example: we have a parent task, which spawns two child tasks to do the actual work, and then at the end of the
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 block it switches into full-time parenting mode while waiting for them to finish. But now instead of just calling
trio.run -> [async function] -> ... -> [async function] -> trio.whatever
5, the children use some of Trio’s networking APIs.

Trước tiên, hãy nhìn vào cha mẹ:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
4

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
5

Đầu tiên chúng tôi gọi

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
91 để tạo kết nối TCP đến máy chủ.
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
92 là một địa chỉ IP ma thuật có nghĩa là máy tính mà tôi đang chạy trên đường, vì vậy điều này kết nối chúng tôi với bất kỳ chương trình nào trên máy tính cục bộ đang sử dụng
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
93 làm điểm liên lạc của nó. Hàm này trả về một đối tượng triển khai giao diện Trio từ
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
94, cung cấp cho chúng tôi các phương thức gửi và nhận byte và để đóng kết nối khi chúng tôi hoàn thành. Chúng tôi sử dụng một khối
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 để đảm bảo rằng chúng tôi đóng kết nối - không phải là vấn đề lớn trong một ví dụ đồ chơi như thế này, nhưng đó là một thói quen tốt để tham gia, và bộ ba được thiết kế để làm cho các khối
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
07 và
# On PyPy:
>>>> trio.run(broken_double_sleep, 3)
*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
>>>> # what the ... ?? not even a warning!

>>>> # but forcing a garbage collection gives us a warning:
>>>> import gc
>>>> gc.collect()
/home/njs/pypy-3.8-nightly/lib-python/3/importlib/_bootstrap.py:191: RuntimeWarning: coroutine 'sleep' was never awaited
if _module_locks.get(name) is wr:    # XXX PyPy fix?
0
>>>>
8 dễ sử dụng.

Cuối cùng, chúng tôi bắt đầu hai nhiệm vụ trẻ em và chuyển từng công việc tham chiếu đến luồng. (Đây cũng là một ví dụ điển hình về cách

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
28 cho phép bạn chuyển các đối số vị trí cho hàm được sinh ra.)

Công việc đầu tiên của chúng tôi là gửi dữ liệu đến máy chủ:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
6

Nó sử dụng một vòng lặp xen kẽ giữa việc gọi

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
99 để gửi một số dữ liệu (đây là phương thức bạn sử dụng để gửi dữ liệu trên bất kỳ loại luồng bộ ba nào), và sau đó ngủ trong một giây để tránh thực hiện cuộn đầu ra quá nhanh trên thiết bị đầu cuối của bạn.

Và công việc của nhiệm vụ thứ hai là xử lý dữ liệu mà máy chủ gửi lại:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
7

Nó sử dụng vòng lặp

def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
15 để tìm nạp dữ liệu từ máy chủ. Ngoài ra, nó có thể sử dụng
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01, trái ngược với
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02, nhưng sử dụng
def print_double(x):
    print(await async_double(x))   # <-- SyntaxError here
15 tiết kiệm một số nồi hơi.

Và bây giờ chúng tôi đã sẵn sàng để nhìn vào máy chủ.

Một máy chủ Echo

Như thường lệ, trước tiên hãy nhìn vào toàn bộ mọi thứ, và sau đó chúng tôi sẽ thảo luận về các phần:

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
8

import trio

async def async_double(x):
    return 2 * x

trio.run(async_double, 3)  # returns 6
9

Hãy bắt đầu với

async def print_double(x):
    print(await async_double(x))   # <-- OK!
04, chỉ dài một dòng:

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
0

Những gì điều này làm là gọi

async def print_double(x):
    print(await async_double(x))   # <-- OK!
05, đây là bộ ba chức năng tiện lợi cung cấp chạy mãi mãi (hoặc ít nhất là cho đến khi bạn nhấn Control-C hoặc hủy bỏ nó). Chức năng này thực hiện một số điều hữu ích:

  • Nó tạo ra một vườn ươm trong nội bộ, để máy chủ của chúng tôi có thể xử lý nhiều kết nối cùng một lúc.

  • Nó lắng nghe các kết nối TCP đến trên

    def print_double(x):
        print(await async_double(x))   # <-- SyntaxError here
    
    93 được chỉ định.

  • Bất cứ khi nào một kết nối đến, nó bắt đầu một tác vụ mới chạy chức năng chúng ta vượt qua (trong ví dụ này, nó ____ ____207) và chuyển nó một luồng đại diện cho kết nối đó.

  • Khi mỗi tác vụ thoát ra, nó đảm bảo đóng kết nối tương ứng. .

Vì vậy,

async def print_double(x):
    print(await async_double(x))   # <-- OK!
05 khá tiện dụng! Phần này hoạt động khá giống với bất kỳ máy chủ nào, cho dù đó là máy chủ Echo, máy chủ HTTP, máy chủ SSH hay bất cứ điều gì, vì vậy thật hợp lý khi kết hợp tất cả trong một chức năng trợ giúp như thế này.

Bây giờ, hãy để Lôi nhìn vào

async def print_double(x):
    print(await async_double(x))   # <-- OK!
07, xử lý từng kết nối của khách hàng - vì vậy nếu có nhiều máy khách, có thể có nhiều cuộc gọi đến
async def print_double(x):
    print(await async_double(x))   # <-- OK!
07 chạy cùng một lúc. Đây là nơi chúng tôi thực hiện máy chủ của chúng tôi. Điều này khá đơn giản để hiểu, bởi vì nó sử dụng các chức năng luồng tương tự mà chúng ta đã thấy trong phần cuối:

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
1

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
2

Đối số

async def print_double(x):
    print(await async_double(x))   # <-- OK!
13 được cung cấp bởi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
05 và là đầu kia của kết nối chúng tôi đã thực hiện trong máy khách: vì vậy dữ liệu mà khách hàng chuyển đến
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 sẽ xuất hiện ở đây. Sau đó, chúng tôi có một khối
async def print_double(x):
    print(await async_double(x))   # <-- OK!
16 được thảo luận dưới đây và cuối cùng, vòng lặp máy chủ xen kẽ giữa việc đọc một số dữ liệu từ ổ cắm và sau đó gửi lại một lần nữa (trừ khi đóng ổ cắm, trong trường hợp chúng tôi thoát).

Vì vậy, những gì mà

async def print_double(x):
    print(await async_double(x))   # <-- OK!
16 khối cho? Hãy nhớ rằng trong bộ ba, như Python nói chung, các trường hợp ngoại lệ tiếp tục lan truyền cho đến khi họ bị bắt. Ở đây chúng tôi nghĩ rằng nó hợp lý có thể có những ngoại lệ bất ngờ và chúng tôi muốn cô lập điều đó để thực hiện một vụ tai nạn nhiệm vụ này, mà không phải hạ gục toàn bộ chương trình. Ví dụ: nếu máy khách đóng kết nối sai thời điểm thì có thể mã này sẽ gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 trên kết nối đã đóng và nhận
async def print_double(x):
    print(await async_double(x))   # <-- OK!
19; Điều đó thật đáng tiếc, và trong một chương trình nghiêm túc hơn, chúng tôi có thể muốn xử lý nó một cách rõ ràng hơn, nhưng nó không cho thấy vấn đề cho bất kỳ kết nối nào khác. Mặt khác, nếu ngoại lệ là một cái gì đó giống như
async def print_double(x):
    print(await async_double(x))   # <-- OK!
20, chúng tôi muốn điều đó tuyên truyền vào nhiệm vụ cha mẹ và khiến toàn bộ chương trình thoát ra. Để thể hiện điều này, chúng tôi sử dụng khối
async def print_double(x):
    print(await async_double(x))   # <-- OK!
16 với trình xử lý
async def print_double(x):
    print(await async_double(x))   # <-- OK!
22.

Nói chung, bộ ba để bạn quyết định xem bạn có muốn xử lý các ngoại lệ hay không, giống như Python nói chung.

Thử nó ra

Mở một vài thiết bị đầu cuối, chạy

async def print_double(x):
    print(await async_double(x))   # <-- OK!
23 trong một, chạy
async def print_double(x):
    print(await async_double(x))   # <-- OK!
24 trong một thiết bị khác và xem tin nhắn cuộn theo! Khi bạn cảm thấy buồn chán, bạn có thể thoát bằng cách nhấn Control-C.

Một số điều cần thử:

  • Mở một số thiết bị đầu cuối và chạy nhiều máy khách cùng một lúc, tất cả đều nói chuyện với cùng một máy chủ.

  • Xem máy chủ phản ứng như thế nào khi bạn nhấn Control-C trên máy khách.

  • Xem máy khách phản ứng như thế nào khi bạn nhấn Control-C trên máy chủ.

Kiểm soát dòng chảy trong máy khách Echo và máy chủ của chúng tôi

Ở đây, một câu hỏi mà bạn có thể tự hỏi: Tại sao khách hàng của chúng tôi sử dụng hai tác vụ riêng biệt để gửi và nhận, thay vì một nhiệm vụ thay thế giữa chúng - như máy chủ có? Ví dụ: khách hàng của chúng tôi có thể sử dụng một tác vụ như:

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
3

Hóa ra có hai vấn đề với điều này - một trẻ vị thành niên và một chuyên ngành. Cả hai liên quan đến kiểm soát dòng chảy. Vấn đề nhỏ là khi chúng tôi gọi

async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 ở đây, chúng tôi không chờ đợi tất cả các dữ liệu có sẵn;
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 trả về ngay khi có bất kỳ dữ liệu nào có sẵn. Nếu
async def print_double(x):
    print(await async_double(x))   # <-- OK!
27 nhỏ, thì các hệ điều hành / mạng / máy chủ / máy chủ của chúng tôi có thể sẽ giữ tất cả lại với nhau trong một khối, nhưng không có gì đảm bảo. Máy chủ gửi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
28 thì chúng ta có thể nhận được
async def print_double(x):
    print(await async_double(x))   # <-- OK!
28 hoặc
async def print_double(x):
    print(await async_double(x))   # <-- OK!
30
async def print_double(x):
    print(await async_double(x))   # <-- OK!
31 hoặc
async def print_double(x):
    print(await async_double(x))   # <-- OK!
32
async def print_double(x):
    print(await async_double(x))   # <-- OK!
33
async def print_double(x):
    print(await async_double(x))   # <-- OK!
34
async def print_double(x):
    print(await async_double(x))   # <-- OK!
34 .

Và nơi này sẽ đặc biệt sai là nếu chúng ta thấy mình trong tình huống

async def print_double(x):
    print(await async_double(x))   # <-- OK!
27 đủ lớn để nó vượt qua một số ngưỡng nội bộ và hệ điều hành hoặc mạng quyết định luôn chia thành nhiều mảnh. Bây giờ trên mỗi lần đi qua vòng lặp, chúng tôi gửi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
39 byte, nhưng đọc ít hơn thế. Kết quả là một cái gì đó giống như rò rỉ bộ nhớ: chúng tôi sẽ kết thúc với ngày càng nhiều dữ liệu được sao lưu trong mạng, cho đến khi cuối cùng một cái gì đó bị phá vỡ.

Ghi chú

Nếu bạn tò mò làm thế nào mọi thứ phá vỡ, thì bạn có thể sử dụng đối số tùy chọn của ____ 201 để đặt giới hạn về số lượng byte bạn đọc mỗi lần và xem điều gì sẽ xảy ra.

Chúng tôi có thể khắc phục điều này bằng cách theo dõi lượng dữ liệu mà chúng tôi mong đợi tại mỗi thời điểm, và sau đó tiếp tục gọi

async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 cho đến khi chúng tôi nhận được tất cả:

import trio

async def double_sleep(x):
    await trio.sleep(2 * x)

trio.run(double_sleep, 3)  # does nothing for 6 seconds then returns
4

Đây là một chút cồng kềnh, nhưng nó sẽ giải quyết vấn đề này.

Mặc dù vậy, có một vấn đề khác, mà sâu hơn. Chúng tôi vẫn còn xen kẽ giữa việc gửi và nhận. Lưu ý rằng khi chúng tôi gửi dữ liệu, chúng tôi sử dụng

trio.run -> double_sleep -> trio.sleep
4: Điều này có nghĩa là việc gửi có thể có khả năng chặn. Lý do tại sao điều này xảy ra? Bất kỳ dữ liệu nào chúng tôi gửi trước tiên đều đi vào bộ đệm hệ điều hành và từ đó lên mạng, sau đó một bộ đệm hệ điều hành khác trên máy tính nhận, trước khi chương trình nhận cuối cùng gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 để lấy dữ liệu ra khỏi các bộ đệm này. Nếu chúng tôi gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 với một lượng nhỏ dữ liệu, thì nó sẽ đi vào các bộ đệm này và
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 trả lại ngay lập tức. Nhưng nếu chúng tôi gửi đủ dữ liệu đủ nhanh, cuối cùng các bộ đệm sẽ lấp đầy và
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 sẽ chặn cho đến khi bên từ xa gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 và giải phóng một số không gian.

Bây giờ, hãy để ý nghĩ về điều này từ quan điểm của máy chủ. Mỗi lần gọi

async def print_double(x):
    print(await async_double(x))   # <-- OK!
01, nó sẽ nhận được một số dữ liệu mà nó cần gửi lại. Và cho đến khi nó gửi lại, dữ liệu đang ngồi xung quanh lấy bộ nhớ. Máy tính có số lượng RAM hữu hạn, vì vậy nếu máy chủ của chúng tôi hoạt động tốt thì tại một số điểm, nó cần ngừng gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 cho đến khi nó loại bỏ một số dữ liệu cũ bằng cách thực hiện cuộc gọi của riêng mình đến
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02. Vì vậy, đối với máy chủ, thực sự là tùy chọn khả thi duy nhất là xen kẽ giữa việc nhận và gửi.

Nhưng chúng ta cần nhớ rằng nó không chỉ là ứng dụng khách của khách hàng đến

async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 có thể chặn: Máy chủ gọi đến
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 cũng có thể gặp phải tình huống chặn cho đến khi khách hàng gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01. Vì vậy, nếu máy chủ đang chờ
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 kết thúc trước khi gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 và khách hàng của chúng tôi cũng chờ
async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 kết thúc trước khi gọi ________ 201, chúng tôi có vấn đề! Máy khách đã giành được cuộc gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 cho đến khi máy chủ gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 và máy chủ đã giành được cuộc gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 cho đến khi khách hàng gọi
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01. Nếu khách hàng của chúng tôi được viết để thay thế giữa việc gửi và nhận, và phần dữ liệu mà nó cố gắng gửi là đủ lớn (ví dụ: 10 megabyte có thể sẽ thực hiện nó trong hầu hết các cấu hình), thì hai quá trình sẽ bị bế tắc.

Đạo đức: Trio cung cấp cho bạn các công cụ mạnh mẽ để quản lý thực thi tuần tự và đồng thời. Trong ví dụ này, chúng tôi đã thấy rằng máy chủ cần

async def print_double(x):
    print(await async_double(x))   # <-- OK!
62 và
async def print_double(x):
    print(await async_double(x))   # <-- OK!
01 để xen kẽ theo trình tự, trong khi khách hàng cần họ chạy đồng thời và cả hai đều đơn giản để thực hiện. Nhưng khi bạn thực hiện mã mạng như thế này thì điều quan trọng là phải suy nghĩ cẩn thận về điều khiển và đệm dòng chảy, bởi vì nó tùy thuộc vào bạn để chọn chế độ thực thi đúng!

Các thư viện Async phổ biến khác như xoắn và

async def print_double(x):
    print(await async_double(x))   # <-- OK!
64 có xu hướng đưa ra những vấn đề này bằng cách ném vào bộ đệm không giới hạn ở khắp mọi nơi. Điều này có thể tránh bế tắc, nhưng có thể giới thiệu các vấn đề của riêng mình và đặc biệt có thể gây khó khăn cho việc giữ cách sử dụng bộ nhớ và độ trễ trong tầm kiểm soát. Mặc dù cả hai cách tiếp cận đều có lợi thế của họ, Trio đảm nhận vị trí rằng nó tốt hơn để phơi bày vấn đề tiềm ẩn trực tiếp nhất có thể và cung cấp các công cụ tốt để đối đầu với nó.

Ghi chú

Nếu bạn muốn thử và thực hiện bế tắc có mục đích để tự mình xem và bạn sử dụng Windows, thì bạn có thể cần chia cuộc gọi

async def print_double(x):
    print(await async_double(x))   # <-- OK!
02 thành hai cuộc gọi mà mỗi người gửi một nửa dữ liệu. Điều này là do Windows có cách xử lý bộ đệm có phần khác thường.

Khi mọi thứ đi sai: thời gian chờ, hủy bỏ và ngoại lệ trong các nhiệm vụ đồng thời

TODO: Đưa ra một ví dụ bằng cách sử dụng

async def print_double(x):
    print(await async_double(x))   # <-- OK!
66

TODO: Giải thích

async def print_double(x):
    print(await async_double(x))   # <-- OK!
67

TODO: Giải thích cách hủy bỏ cũng được sử dụng khi một đứa trẻ tăng ngoại lệ

TODO: Có thể một cuộc thảo luận ngắn gọn về xử lý

async def print_double(x):
    print(await async_double(x))   # <-- OK!
20?