Tổng quan về khái niệm của asyncio¶
Bài viết HOWTO này nhằm giúp bạn xây dựng một mô hình tinh thần chắc chắn về cách hoạt động cơ bản của asyncio, giúp bạn hiểu cách thức và lý do đằng sau các mẫu được đề xuất.
Bạn có thể tò mò về một số khái niệm chính của asyncio. Đến cuối bài viết này, bạn sẽ có thể thoải mái trả lời những câu hỏi sau:
Điều gì đang xảy ra đằng sau khi một đối tượng được chờ đợi?
asynciophân biệt như thế nào giữa một tác vụ không cần thời gian CPU (chẳng hạn như yêu cầu mạng hoặc đọc tệp) với một tác vụ thực hiện (chẳng hạn như tính toán n-giai thừa)?Cách viết một biến thể không đồng bộ của một thao tác, chẳng hạn như chế độ ngủ không đồng bộ hoặc yêu cầu cơ sở dữ liệu.
Xem thêm
Zz000zz đã truyền cảm hứng cho bài viết HOWTO này của Alexander Nordin.
YouTube tutorial series chuyên sâu về
asyncionày được tạo bởi thành viên nhóm nòng cốt Python, Łukasz Langa.500 Lines or Less: A Web Crawler With asyncio Coroutines của A. Jesse Jiryu Davis và Guido van Rossum.
Tổng quan về khái niệm phần 1: cấp độ cao¶
Trong phần 1, chúng ta sẽ đề cập đến các khối xây dựng chính, cấp cao của asyncio: vòng lặp sự kiện, hàm coroutine, đối tượng coroutine, tác vụ và await.
Vòng lặp sự kiện¶
Mọi thứ trong asyncio xảy ra liên quan đến vòng lặp sự kiện. Đó là ngôi sao của chương trình. Nó giống như một người chỉ huy dàn nhạc. Đó là việc quản lý tài nguyên ở hậu trường. Một số quyền lực được trao rõ ràng cho nó, nhưng phần lớn khả năng hoàn thành công việc của nó đến từ sự tôn trọng và hợp tác của những con ong thợ.
Nói một cách kỹ thuật hơn, vòng lặp sự kiện chứa một tập hợp các công việc sẽ được thực hiện. Một số công việc được bạn trực tiếp thêm vào và một số công việc được thêm gián tiếp bởi asyncio. Vòng lặp sự kiện nhận một công việc từ công việc tồn đọng của nó và gọi nó (hoặc "trao quyền kiểm soát cho nó"), tương tự như gọi một hàm và sau đó công việc đó sẽ chạy. Khi nó tạm dừng hoặc hoàn thành, nó sẽ trả lại quyền điều khiển cho vòng lặp sự kiện. Vòng lặp sự kiện sau đó sẽ chọn một công việc khác từ nhóm của nó và gọi nó. Bạn có thể roughly coi tập hợp các công việc như một hàng đợi: các công việc được thêm vào và sau đó xử lý từng công việc một, nói chung (nhưng không phải luôn luôn) theo thứ tự. Quá trình này lặp lại vô thời hạn, với vòng lặp sự kiện quay trở đi vô tận. Nếu không còn công việc nào đang chờ thực thi, vòng lặp sự kiện sẽ đủ thông minh để nghỉ ngơi và tránh lãng phí chu kỳ CPU một cách không cần thiết, đồng thời sẽ quay trở lại khi có nhiều việc phải làm hơn.
Việc thực hiện hiệu quả phụ thuộc vào việc chia sẻ công việc tốt và hợp tác; một công việc tham lam có thể kiểm soát chặt chẽ và khiến các công việc khác chết đói, khiến cách tiếp cận vòng lặp sự kiện tổng thể trở nên vô dụng.
nhập asyncio
# This tạo một vòng lặp sự kiện và quay vòng vô thời hạn
# its tuyển tập việc làm.
sự kiện_loop = asyncio.new_event_loop()
sự kiện_loop.run_forever()
Các hàm và coroutine không đồng bộ¶
Đây là một hàm Python cơ bản, nhàm chán:
def hello_printer():
in(
"Xin chào, tôi là một thợ in tầm thường, đơn giản, mặc dù tôi có tất cả những gì tôi có"
"điều cần thiết trong cuộc sống -- \ngiấy tươi và con bạch tuộc thân yêu của tôi "
"đối tác của tội phạm."
)
Việc gọi một hàm thông thường sẽ gọi logic hoặc phần thân của nó:
>>> xin chào_printer()
Xin chào, tôi là một thợ in tầm thường, đơn giản, mặc dù tôi có tất cả những gì tôi cần trong cuộc sống --
tờ báo mới và con bạch tuộc thân yêu của tôi đồng hành cùng tội phạm.
async def, trái ngược với def đơn giản, làm cho hàm này trở thành một hàm không đồng bộ (hoặc "hàm coroutine"). Việc gọi nó sẽ tạo và trả về một đối tượng coroutine.
async def Loudmouth_penguin(magic_number: int):
in(
"Tôi là một chú chim cánh cụt biết nói siêu đặc biệt. Tuyệt vời hơn nhiều so với chiếc máy in đó."
f"Nhân tiện, con số may mắn của tôi là: {magic_number}."
)
Gọi hàm async, loudmouth_penguin, không thực thi câu lệnh in; thay vào đó, nó tạo ra một đối tượng coroutine:
>>> Loumouth_penguin(magic_number=3)
<đối tượng coroutine Loumouth_penguin tại 0x104ed2740>
Các thuật ngữ "hàm coroutine" và "đối tượng coroutine" thường được gọi là coroutine. Điều đó có thể gây nhầm lẫn! Trong bài viết này, coroutine đề cập cụ thể đến một đối tượng coroutine, hay chính xác hơn là một phiên bản của types.CoroutineType (coroutine gốc). Lưu ý rằng coroutine cũng có thể tồn tại dưới dạng phiên bản của collections.abc.Coroutine -- một điểm khác biệt quan trọng đối với việc kiểm tra loại.
Một coroutine đại diện cho phần thân hoặc logic của hàm. Một coroutine phải được bắt đầu một cách rõ ràng; một lần nữa, việc chỉ tạo coroutine sẽ không khởi động nó. Đáng chú ý, coroutine có thể bị tạm dừng và tiếp tục ở nhiều điểm khác nhau trong nội dung hàm. Khả năng tạm dừng và tiếp tục đó là yếu tố cho phép hành vi không đồng bộ!
Coroutine và các hàm coroutine được xây dựng bằng cách tận dụng chức năng của generators và generator functions. Hãy nhớ lại, hàm tạo là một hàm yields, như hàm này:
chắc chắn get_random_number():
# This sẽ là một trình tạo số ngẫu nhiên tồi!
in ("Xin chào")
năng suất 1
in ("Xin chào")
năng suất 7
in ("Xin chào")
năng suất 4
...
Tương tự như hàm coroutine, việc gọi hàm tạo sẽ không chạy hàm đó. Thay vào đó, nó tạo một đối tượng trình tạo:
>>> get_random_number()
<đối tượng máy phát điện get_random_number tại 0x1048671c0>
Bạn có thể tiến hành yield tiếp theo của trình tạo bằng cách sử dụng chức năng next() tích hợp sẵn. Nói cách khác, máy phát điện chạy rồi tạm dừng. Ví dụ:
>>> trình tạo = get_random_number()
>>> tiếp theo(máy phát điện)
Xin chào
1
>>> tiếp theo(máy phát điện)
xin chào
7
Nhiệm vụ¶
Nói một cách đại khái, tasks là các coroutine (không phải các hàm coroutine) được gắn với một vòng lặp sự kiện. Một tác vụ cũng duy trì một danh sách các hàm gọi lại mà tầm quan trọng của chúng sẽ trở nên rõ ràng sau khi chúng ta thảo luận về await. Cách được đề xuất để tạo tác vụ là thông qua asyncio.create_task().
Việc tạo một tác vụ sẽ tự động lên lịch thực hiện tác vụ đó (bằng cách thêm lệnh gọi lại để chạy tác vụ đó trong danh sách việc cần làm của vòng lặp sự kiện, tức là tập hợp các công việc).
asyncio tự động liên kết các nhiệm vụ với vòng lặp sự kiện cho bạn. Liên kết tự động này được thiết kế có chủ đích vào asyncio vì mục đích đơn giản. Nếu không có nó, bạn sẽ phải theo dõi đối tượng vòng lặp sự kiện và chuyển nó đến bất kỳ hàm coroutine nào muốn tạo tác vụ, thêm sự lộn xộn dư thừa vào mã của bạn.
coroutine = Loumouth_penguin(magic_number=5)
# This tạo một đối tượng Task và lên lịch thực hiện nó thông qua vòng lặp sự kiện.
nhiệm vụ = asyncio.create_task(coroutine)
Trước đó, chúng tôi đã tạo vòng lặp sự kiện theo cách thủ công và đặt nó chạy vĩnh viễn. Trong thực tế, bạn nên sử dụng (và thường thấy) asyncio.run() để quản lý vòng lặp sự kiện và đảm bảo coroutine được cung cấp kết thúc trước khi tiếp tục. Ví dụ: nhiều chương trình không đồng bộ tuân theo thiết lập này
nhập asyncio
async def main():
# Perform tất cả những thứ không đồng bộ kỳ quặc, hoang dã...
...
nếu __name__ == "__main__":
asyncio.run(chính())
chương trình # The sẽ không đạt được câu lệnh in sau cho đến khi
# coroutine main() kết thúc.
print("coroutine main() đã xong!")
Điều quan trọng cần lưu ý là bản thân tác vụ không được thêm vào vòng lặp sự kiện mà chỉ có lệnh gọi lại tác vụ. Điều này quan trọng nếu đối tượng tác vụ bạn tạo là rác được thu thập trước khi nó được vòng lặp sự kiện gọi. Ví dụ, hãy xem xét chương trình này:
1async def xin chào():
2 in ("xin chào!")
3
4async def main():
5 asyncio.create_task(xin chào())
6 Hướng dẫn không đồng bộ # Other chạy trong một thời gian
7 # and nhường quyền kiểm soát cho vòng lặp sự kiện...
8 ...
9
10asyncio.run(chính())
Bởi vì không có tham chiếu đến đối tượng tác vụ được tạo trên dòng 5, nên nó sẽ được thu thập trước khi vòng lặp sự kiện gọi nó. Các hướng dẫn sau này trong điều khiển tay coroutine main() sẽ quay lại vòng lặp sự kiện để có thể gọi các công việc khác. Cuối cùng, khi vòng lặp sự kiện cố gắng chạy tác vụ, nó có thể thất bại và phát hiện ra đối tượng tác vụ không tồn tại! Điều này cũng có thể xảy ra ngay cả khi một coroutine giữ tham chiếu đến một tác vụ nhưng hoàn thành trước khi tác vụ đó kết thúc. Khi coroutine thoát, các biến cục bộ sẽ nằm ngoài phạm vi và có thể bị thu gom rác. Trong thực tế, asyncio và trình thu gom rác của Python làm việc khá chăm chỉ để đảm bảo điều này không xảy ra. Nhưng đó không phải là lý do để liều lĩnh!
chờ đợi¶
await là một từ khóa Python thường được sử dụng theo một trong hai cách khác nhau:
chờ đợi nhiệm vụ
đang chờ coroutine
Theo một cách quan trọng, hành vi của await phụ thuộc vào loại đối tượng đang được chờ đợi.
Việc chờ một tác vụ sẽ nhường quyền kiểm soát từ tác vụ hoặc coroutine hiện tại cho vòng lặp sự kiện. Trong quá trình từ bỏ quyền kiểm soát, một số điều quan trọng sẽ xảy ra. Chúng tôi sẽ sử dụng ví dụ mã sau để minh họa:
async def plant_a_tree():
dig_the_hole_task = asyncio.create_task(dig_the_hole())
đang chờ đào_the_hole_task
# Other hướng dẫn liên quan đến việc trồng cây.
...
Trong ví dụ này, hãy tưởng tượng vòng lặp sự kiện đã chuyển quyền điều khiển đến điểm bắt đầu của coroutine plant_a_tree(). Như đã thấy ở trên, coroutine tạo một tác vụ và sau đó chờ tác vụ đó. Lệnh await dig_the_hole_task thêm một lệnh gọi lại (sẽ tiếp tục plant_a_tree()) vào danh sách các lệnh gọi lại của đối tượng dig_the_hole_task. Và sau đó, lệnh nhường quyền điều khiển cho vòng lặp sự kiện. Một thời gian sau, vòng lặp sự kiện sẽ chuyển quyền điều khiển cho dig_the_hole_task và tác vụ sẽ hoàn thành bất cứ điều gì nó cần làm. Sau khi tác vụ kết thúc, tác vụ sẽ thêm các lệnh gọi lại khác nhau vào vòng lặp sự kiện, trong trường hợp này là lệnh gọi tiếp tục plant_a_tree().
Nói chung, khi tác vụ được chờ kết thúc (dig_the_hole_task), tác vụ ban đầu hoặc coroutine (plant_a_tree()) sẽ được thêm trở lại danh sách việc cần làm của vòng lặp sự kiện để tiếp tục.
Đây là một mô hình tinh thần cơ bản nhưng đáng tin cậy. Trong thực tế, việc chuyển giao điều khiển phức tạp hơn một chút nhưng không nhiều. Trong phần 2, chúng ta sẽ tìm hiểu chi tiết để thực hiện điều này.
Unlike tasks, awaiting a coroutine does not hand control back to the event loop! Gói coroutine vào một nhiệm vụ trước, sau đó chờ đợi điều đó sẽ nhường lại quyền kiểm soát. Hoạt động của await coroutine thực sự giống như việc gọi hàm Python đồng bộ, thông thường. Hãy xem xét chương trình này:
nhập asyncio
async def coro_a():
print("Tôi là coro_a(). Xin chào!")
async def coro_b():
print("Tôi là coro_b(). Tôi chắc chắn hy vọng không ai chiếm giữ vòng lặp sự kiện...")
async def main():
task_b = asyncio.create_task(coro_b())
num_repeat = 3
cho _ trong phạm vi(num_repeat):
đang chờ coro_a()
đang chờ task_b
asyncio.run(chính())
Câu lệnh đầu tiên trong coroutine main() tạo task_b và lên lịch thực thi nó thông qua vòng lặp sự kiện. Sau đó, coro_a() được chờ đợi nhiều lần. Kiểm soát không bao giờ chuyển sang vòng lặp sự kiện, đó là lý do tại sao chúng ta thấy đầu ra của cả ba lệnh gọi coro_a() trước đầu ra của coro_b():
Tôi là coro_a(). CHÀO!
Tôi là coro_a(). CHÀO!
Tôi là coro_a(). CHÀO!
Tôi là coro_b(). Tôi chắc chắn hy vọng không ai nắm bắt được vòng lặp sự kiện...
Nếu chúng ta thay đổi await coro_a() thành await asyncio.create_task(coro_a()), hành vi sẽ thay đổi. Coroutine main() nhường quyền kiểm soát cho vòng lặp sự kiện bằng câu lệnh đó. Sau đó, vòng lặp sự kiện sẽ xử lý các công việc tồn đọng của nó, gọi task_b và sau đó là tác vụ kết thúc coro_a() trước khi tiếp tục coroutine main().
Tôi là coro_b(). Tôi chắc chắn hy vọng không ai nắm bắt được vòng lặp sự kiện...
Tôi là coro_a(). CHÀO!
Tôi là coro_a(). CHÀO!
Tôi là coro_a(). CHÀO!
Hành vi này của await coroutine có thể khiến rất nhiều người cảm thấy khó chịu! Ví dụ đó nêu bật việc chỉ sử dụng await coroutine có thể vô tình chiếm quyền kiểm soát của các tác vụ khác và trì hoãn vòng lặp sự kiện một cách hiệu quả. asyncio.run() có thể giúp bạn phát hiện những trường hợp như vậy thông qua cờ debug=True, cờ này kích hoạt debug mode. Trong số những thứ khác, nó sẽ ghi lại bất kỳ coroutine nào độc quyền thực thi trong 100 mili giây trở lên.
Thiết kế này cố tình đánh đổi một số khái niệm rõ ràng xung quanh việc sử dụng await để cải thiện hiệu suất. Mỗi khi một nhiệm vụ được chờ đợi, quyền điều khiển cần được chuyển từ ngăn xếp cuộc gọi đến vòng lặp sự kiện. Điều đó nghe có vẻ nhỏ nhặt, nhưng trong một chương trình lớn có nhiều câu lệnh await và một ngăn xếp cuộc gọi sâu, chi phí đó có thể tăng thêm lực cản hiệu suất đáng kể.
Tổng quan về khái niệm phần 2: đai ốc và bu lông¶
Phần 2 đi sâu vào chi tiết về các cơ chế mà asyncio sử dụng để quản lý luồng điều khiển. Đây là nơi phép thuật xảy ra. Sau phần này, bạn sẽ biết await thực hiện những gì ở hậu trường và cách tạo các toán tử không đồng bộ của riêng mình.
Hoạt động bên trong của coroutine¶
asyncio tận dụng bốn thành phần để vượt qua quyền kiểm soát.
coroutine.send(arg) là phương thức được sử dụng để bắt đầu hoặc tiếp tục một coroutine. Nếu coroutine bị tạm dừng và hiện đang được tiếp tục lại, đối số arg sẽ được gửi dưới dạng giá trị trả về của câu lệnh yield ban đầu đã tạm dừng nó. Nếu coroutine được sử dụng lần đầu tiên (trái ngược với việc tiếp tục sử dụng), arg phải là None.
1lớp đá:
2 chắc chắn __await__(tự):
3 value_sent_in = năng suất 7
4 print(f"Rock.__await__ đang tiếp tục với giá trị: {value_sent_in}.")
5 trả về value_sent_in
6
7async def main():
8 print("Bắt đầu coroutine main().")
9 đá = Đá()
10 print("Đang chờ đá...")
11 value_from_rock = chờ đá
12 print(f"Coroutine đã nhận được giá trị: {value_from_rock} từ rock.")
13 trở lại 23
14
15coroutine = chính()
16trung gian_result = coroutine.send(Không có)
17print(f"Coroutine đã tạm dừng và trả về giá trị trung gian: {intermediate_result}.")
18
19print(f"Tiếp tục coroutine và gửi giá trị: 42.")
20thử:
21 coroutine.send(42)
22ngoại trừ StopIteration là e:
23 return_value = e.value
24print(f"Coroutine main() đã hoàn thành và cung cấp giá trị: {returned_value}.")
yield, như thường lệ, tạm dừng thực thi và trả lại quyền điều khiển cho người gọi. Trong ví dụ trên, yield, trên dòng 3, được gọi bởi ... = await rock trên dòng 11. Nói rộng hơn, await gọi phương thức __await__() của đối tượng đã cho. await còn thực hiện một điều rất đặc biệt nữa: nó truyền bá (hoặc "chuyển tiếp") bất kỳ yield nào nó nhận được trong chuỗi cuộc gọi. Trong trường hợp này, nó quay lại ... = coroutine.send(None) ở dòng 16.
Coroutine được tiếp tục thông qua lệnh gọi coroutine.send(42) trên dòng 21. Coroutine quay trở lại từ nơi nó yielded (hoặc tạm dừng) trên dòng 3 và thực thi các câu lệnh còn lại trong phần nội dung của nó. Khi một coroutine kết thúc, nó sẽ đưa ra một ngoại lệ StopIteration với giá trị trả về được đính kèm trong thuộc tính value.
Đoạn mã đó tạo ra kết quả này:
Bắt đầu coroutine main().
Đang chờ đá...
Coroutine tạm dừng và trả về giá trị trung gian: 7.
Tiếp tục coroutine và gửi giá trị: 42.
Rock.__await__ đang tiếp tục với giá trị: 42.
Coroutine nhận được giá trị: 42 từ rock.
Coroutine main() đã hoàn thành và cung cấp giá trị: 23.
Bạn nên dừng lại một chút ở đây và đảm bảo rằng bạn đã làm theo nhiều cách khác nhau để kiểm soát luồng và các giá trị được thông qua. Rất nhiều ý tưởng quan trọng đã được đề cập và cần đảm bảo sự hiểu biết của bạn một cách vững chắc.
Cách duy nhất để nhường (hoặc nhượng lại quyền kiểm soát một cách hiệu quả) từ một coroutine là cho await một đối tượng yields trong phương thức __await__ của nó. Điều đó nghe có vẻ kỳ lạ đối với bạn. Có thể bạn đang nghĩ:
1. What about a
yielddirectly within the coroutine function? The coroutine function becomes an async generator function, a different beast entirely.2. What about a yield from within the coroutine function to a (plain) generator? That causes the error:
SyntaxError: yield from not allowed in a coroutine.This was intentionally designed for the sake of simplicity -- mandating only one way of using coroutines. Initiallyyieldwas barred as well, but was re-accepted to allow for async generators. Despite that,yield fromandawaiteffectively do the same thing.
Hợp đồng tương lai¶
Zz000zz là một đối tượng dùng để biểu thị trạng thái và kết quả của một phép tính. Thuật ngữ này thể hiện ý tưởng về một điều gì đó vẫn sắp xảy ra hoặc chưa xảy ra, và đối tượng là một cách để theo dõi điều gì đó.
Tương lai có một vài thuộc tính quan trọng. Một là trạng thái của nó, có thể là "đang chờ xử lý", "đã hủy" hoặc "đã hoàn tất". Một kết quả khác là kết quả của nó, được đặt khi trạng thái chuyển sang hoàn thành. Không giống như coroutine, tương lai không đại diện cho việc tính toán thực tế sẽ được thực hiện; thay vào đó, nó thể hiện trạng thái và kết quả của phép tính đó, giống như đèn trạng thái (đỏ, vàng hoặc xanh lục) hoặc chỉ báo.
asyncio.Task phân lớp asyncio.Future để có được những khả năng khác nhau này. Phần trước cho biết các tác vụ lưu trữ danh sách các lệnh gọi lại, danh sách này không hoàn toàn chính xác. Trên thực tế, lớp Future thực hiện logic này, lớp Task kế thừa.
Hợp đồng tương lai cũng có thể được sử dụng trực tiếp (không thông qua nhiệm vụ). Nhiệm vụ tự đánh dấu là đã hoàn thành khi coroutine của chúng hoàn tất. Hợp đồng tương lai linh hoạt hơn nhiều và sẽ được đánh dấu là hoàn tất khi bạn nói như vậy. Bằng cách này, chúng là giao diện linh hoạt để bạn tự tạo điều kiện chờ đợi và tiếp tục.
Một asyncio.sleep tự chế¶
Chúng ta sẽ xem qua một ví dụ về cách bạn có thể tận dụng tương lai để tạo ra biến thể giấc ngủ không đồng bộ (async_sleep) của riêng bạn bắt chước asyncio.sleep().
Đoạn mã này đăng ký một số tác vụ với vòng lặp sự kiện, sau đó chờ tác vụ được tạo bởi asyncio.create_task, tác vụ này bao bọc coroutine async_sleep(3). Chúng tôi muốn tác vụ đó chỉ hoàn thành sau ba giây trôi qua nhưng không ngăn các tác vụ khác chạy.
async def other_work():
print("Tôi thích công việc. Công việc làm việc.")
async def main():
# Add một vài nhiệm vụ khác cho vòng lặp sự kiện, vậy nên có điều gì đó
# to làm gì khi ngủ không đồng bộ.
công việc_tác vụ = [
asyncio.create_task(other_work()),
asyncio.create_task(other_work()),
asyncio.create_task(other_work())
]
in(
"Bắt đầu ngủ không đồng bộ vào thời điểm: "
f"{datetime.datetime.now().strftime("%H:%M:%S")}."
)
đang chờ asyncio.create_task(async_sleep(3))
in(
"Đã thực hiện chế độ ngủ không đồng bộ vào thời điểm đó: "
f"{datetime.datetime.now().strftime("%H:%M:%S")}."
)
# asyncio.gather chờ đợi từng tác vụ trong bộ sưu tập một cách hiệu quả.
đang chờ asyncio.gather(*work_tasks)
Dưới đây, chúng tôi sử dụng tương lai để bật quyền kiểm soát tùy chỉnh về thời điểm tác vụ đó sẽ được đánh dấu là hoàn thành. Nếu future.set_result() (phương thức chịu trách nhiệm đánh dấu tương lai đó là đã hoàn thành) không bao giờ được gọi thì tác vụ này sẽ không bao giờ kết thúc. Chúng tôi cũng đã tranh thủ sự trợ giúp của một nhiệm vụ khác mà chúng tôi sẽ xem ngay sau đây, nhiệm vụ này sẽ theo dõi lượng thời gian đã trôi qua và theo đó, hãy gọi future.set_result().
async def async_sleep(giây: float):
tương lai = asyncio.Future()
time_to_wake = time.time() + giây
# Add nhiệm vụ của người theo dõi vào vòng lặp sự kiện.
watcher_task = asyncio.create_task(_sleep_watcher(tương lai, time_to_wake))
# Block cho đến khi tương lai được đánh dấu là hoàn tất.
chờ đợi tương lai
Dưới đây, chúng tôi sử dụng một đối tượng YieldToEventLoop() khá đơn giản cho yield từ phương thức __await__ của nó, nhường quyền kiểm soát cho vòng lặp sự kiện. Điều này thực sự giống như cách gọi asyncio.sleep(0), nhưng cách tiếp cận này mang lại sự rõ ràng hơn, chưa kể việc sử dụng asyncio.sleep có phần gian lận khi trình bày cách triển khai nó!
Như thường lệ, vòng lặp sự kiện sẽ xoay vòng qua các nhiệm vụ của nó, trao cho chúng quyền kiểm soát và nhận lại quyền kiểm soát khi chúng tạm dừng hoặc kết thúc. Zz000zz chạy coroutine _sleep_watcher(...) sẽ được gọi một lần trong toàn bộ chu kỳ của vòng lặp sự kiện. Mỗi lần tiếp tục, nó sẽ kiểm tra thời gian và nếu chưa đủ thì nó sẽ tạm dừng một lần nữa và quay lại vòng lặp sự kiện bằng tay. Khi đã đủ thời gian trôi qua, _sleep_watcher(...) đánh dấu tương lai là hoàn thành và hoàn thành bằng cách thoát khỏi vòng lặp while vô hạn của nó. Do tác vụ trợ giúp này chỉ được gọi một lần trong mỗi chu kỳ của vòng lặp sự kiện, bạn nên lưu ý rằng chế độ ngủ không đồng bộ này sẽ ngủ at least ba giây, thay vì chính xác là ba giây. Lưu ý điều này cũng đúng với asyncio.sleep.
lớp YieldToEventLoop:
chắc chắn __await__(tự):
năng suất
async def _sleep_watcher(tương lai, time_to_wake):
trong khi Đúng:
nếu time.time() >= time_to_wake:
# This đánh dấu tương lai là hoàn tất.
tương lai.set_result(Không có)
phá vỡ
khác:
đang chờ YieldToEventLoop()
Đây là đầu ra của chương trình đầy đủ:
$ python custom-async-sleep.py
Bắt đầu ngủ không đồng bộ vào thời điểm: 14:52:22.
Tôi thích công việc. Công việc làm việc.
Tôi thích công việc. Công việc làm việc.
Tôi thích công việc. Công việc làm việc.
Đã hoàn thành chế độ ngủ không đồng bộ vào lúc: 14:52:25.
Bạn có thể cảm thấy việc triển khai chế độ ngủ không đồng bộ này phức tạp một cách không cần thiết. Và đúng là như vậy. Ví dụ này nhằm thể hiện tính linh hoạt của hợp đồng tương lai bằng một ví dụ đơn giản có thể bắt chước cho các nhu cầu phức tạp hơn. Để tham khảo, bạn có thể triển khai nó mà không cần tương lai, như vậy
async def đơn giản_async_sleep(giây):
time_to_wake = time.time() + giây
trong khi Đúng:
nếu time.time() >= time_to_wake:
trở về
khác:
đang chờ YieldToEventLoop()
Nhưng đó là tất cả bây giờ. Hy vọng rằng bạn đã sẵn sàng tự tin hơn khi tìm hiểu một số chương trình không đồng bộ hoặc xem các chủ đề nâng cao trong rest of the documentation.