Khi nào nên sử dụng async/await python

Python là một trong nhiều ngôn ngữ hỗ trợ một số cách để viết các chương trình không đồng bộ — các chương trình có thể tự do chuyển đổi giữa nhiều tác vụ, tất cả đều chạy cùng một lúc, do đó không có tác vụ nào theo kịp tiến độ của các tác vụ khác

Tuy nhiên, rất có thể bạn đã chủ yếu viết các chương trình Python đồng bộ — các chương trình chỉ thực hiện một việc tại một thời điểm, đợi mỗi tác vụ kết thúc trước khi bắt đầu một tác vụ khác. Chuyển sang không đồng bộ có thể gây khó chịu vì nó yêu cầu học không chỉ cú pháp mới mà còn cả cách suy nghĩ mới về mã của một người.  

Trong bài viết này, chúng ta sẽ khám phá cách biến một chương trình đồng bộ, hiện có thành một chương trình không đồng bộ. Điều này liên quan đến nhiều thứ hơn là chỉ trang trí các chức năng với cú pháp không đồng bộ; .  

[ Cũng có trên InfoWorld. Tìm hiểu các mẹo và thủ thuật Python từ các video Smart Python của Serdar Yegulalp ]

Khi nào nên sử dụng async trong Python

Một chương trình Python phù hợp nhất cho async khi nó có các đặc điểm sau

  • Nó đang cố gắng làm điều gì đó chủ yếu bị ràng buộc bởi I/O hoặc bằng cách đợi một số quy trình bên ngoài hoàn tất, chẳng hạn như đọc mạng lâu dài
  • Nó đang cố gắng thực hiện một hoặc nhiều loại nhiệm vụ đó cùng một lúc, đồng thời có thể xử lý các tương tác của người dùng
  • Các nhiệm vụ trong câu hỏi không nặng về tính toán

Một chương trình Python sử dụng luồng thường là một ứng cử viên tốt để sử dụng async. Chủ đề trong Python là hợp tác; . Các tác vụ không đồng bộ trong Python hoạt động theo cùng một cách. Ngoài ra, async mang lại những lợi thế nhất định so với các luồng

  • Cú pháp
    import asyncio
    
    async def a_function():
        # some async-compatible action that takes a while
    
    def another_function():
        # some sync function, but not a blocking one
    
    async def do_stuff():
        await a_function()
        another_function()
    
    async def main():
        tasks = []
        for _ in range(3):
            tasks.append(asyncio.create_task(do_stuff()))
        await asyncio.gather(tasks)
    
    asyncio.run(main())
    
    8/
    import asyncio
    
    async def a_function():
        # some async-compatible action that takes a while
    
    def another_function():
        # some sync function, but not a blocking one
    
    async def do_stuff():
        await a_function()
        another_function()
    
    async def main():
        tasks = []
        for _ in range(3):
            tasks.append(asyncio.create_task(do_stuff()))
        await asyncio.gather(tasks)
    
    asyncio.run(main())
    
    9 giúp dễ dàng xác định các phần không đồng bộ trong chương trình của bạn. Ngược lại, nhìn thoáng qua thường khó biết phần nào của ứng dụng chạy trong một chuỗi.  
  • Vì các tác vụ không đồng bộ chia sẻ cùng một luồng, nên mọi dữ liệu mà chúng truy cập đều được quản lý tự động bởi GIL (cơ chế riêng của Python để đồng bộ hóa quyền truy cập vào các đối tượng). Các chủ đề thường yêu cầu các cơ chế phức tạp để đồng bộ hóa.  
  • Các tác vụ không đồng bộ dễ quản lý và hủy bỏ hơn các luồng

Không nên sử dụng async nếu chương trình Python của bạn có những đặc điểm này

  • Các tác vụ có chi phí tính toán cao — e. g. , họ đang tính nhẩm số. Công việc tính toán nặng được xử lý tốt nhất với
    import asyncio
    
    async def a_function():
        # some async-compatible action that takes a while
    
    def another_function():
        # some sync function, but not a blocking one
    
    async def do_stuff():
        await a_function()
        another_function()
    
    async def main():
        tasks = []
        for _ in range(3):
            tasks.append(asyncio.create_task(do_stuff()))
        await asyncio.gather(tasks)
    
    asyncio.run(main())
    
    0, cho phép bạn dành toàn bộ chuỗi phần cứng cho từng tác vụ
  • Các nhiệm vụ không được hưởng lợi từ việc xen kẽ. Nếu mỗi tác vụ phụ thuộc vào tác vụ cuối cùng, thì chẳng có ích gì khi bắt chúng chạy không đồng bộ. Điều đó có nghĩa là, nếu chương trình liên quan đến tập hợp các tác vụ nối tiếp, thì bạn có thể chạy từng tập hợp một cách không đồng bộ

Bước 1. Xác định các phần đồng bộ và không đồng bộ trong chương trình của bạn

Mã không đồng bộ Python phải được khởi chạy và quản lý bởi các phần đồng bộ của ứng dụng Python của bạn. Cuối cùng, nhiệm vụ đầu tiên của bạn khi chuyển đổi chương trình thành không đồng bộ là vẽ một đường thẳng giữa các phần đồng bộ hóa và không đồng bộ trong mã của bạn

Trong bài viết trước của chúng tôi về async, chúng tôi đã sử dụng ứng dụng quét web làm ví dụ đơn giản. Các phần không đồng bộ của mã là các quy trình mở kết nối mạng và đọc từ trang web — mọi thứ bạn muốn xen kẽ. Nhưng một phần của chương trình khởi động tất cả những điều đó không phải là không đồng bộ;

Điều quan trọng nữa là phải tách biệt mọi thao tác có khả năng chặn khỏi tính năng không đồng bộ và giữ nó trong phần đồng bộ hóa của ứng dụng của bạn. Chẳng hạn, đọc đầu vào của người dùng từ bảng điều khiển, chặn mọi thứ, kể cả vòng lặp sự kiện không đồng bộ. Do đó, bạn muốn xử lý đầu vào của người dùng trước khi khởi chạy các tác vụ không đồng bộ hoặc sau khi hoàn thành chúng. (Có thể xử lý đầu vào của người dùng một cách không đồng bộ thông qua đa xử lý hoặc phân luồng, nhưng đó là một bài tập nâng cao mà chúng tôi sẽ không đề cập ở đây. )

Một số ví dụ về hoạt động chặn

  • Đầu vào bảng điều khiển (như chúng tôi vừa mô tả)
  • Các tác vụ liên quan đến việc sử dụng nhiều CPU
  • Sử dụng
    import asyncio
    
    async def a_function():
        # some async-compatible action that takes a while
    
    def another_function():
        # some sync function, but not a blocking one
    
    async def do_stuff():
        await a_function()
        another_function()
    
    async def main():
        tasks = []
        for _ in range(3):
            tasks.append(asyncio.create_task(do_stuff()))
        await asyncio.gather(tasks)
    
    asyncio.run(main())
    
    1 để buộc tạm dừng. Lưu ý rằng bạn có thể ngủ bên trong chức năng không đồng bộ bằng cách sử dụng
    import asyncio
    
    async def a_function():
        # some async-compatible action that takes a while
    
    def another_function():
        # some sync function, but not a blocking one
    
    async def do_stuff():
        await a_function()
        another_function()
    
    async def main():
        tasks = []
        for _ in range(3):
            tasks.append(asyncio.create_task(do_stuff()))
        await asyncio.gather(tasks)
    
    asyncio.run(main())
    
    2 để thay thế cho
    import asyncio
    
    async def a_function():
        # some async-compatible action that takes a while
    
    def another_function():
        # some sync function, but not a blocking one
    
    async def do_stuff():
        await a_function()
        another_function()
    
    async def main():
        tasks = []
        for _ in range(3):
            tasks.append(asyncio.create_task(do_stuff()))
        await asyncio.gather(tasks)
    
    asyncio.run(main())
    
    1

Bước 2. Chuyển đổi các chức năng đồng bộ thích hợp thành các chức năng không đồng bộ

Khi bạn biết phần nào trong chương trình của mình sẽ chạy không đồng bộ, bạn có thể phân chia chúng thành các hàm (nếu bạn chưa có) và biến chúng thành các hàm không đồng bộ với từ khóa

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
8. Sau đó, bạn sẽ cần thêm mã vào phần đồng bộ của ứng dụng để chạy mã không đồng bộ và thu thập kết quả từ mã đó nếu cần

Ghi chú. Bạn sẽ muốn kiểm tra chuỗi cuộc gọi của từng chức năng mà bạn đã thực hiện không đồng bộ và đảm bảo rằng chúng không gọi một hoạt động có khả năng chạy dài hoặc chặn. Các chức năng không đồng bộ có thể gọi trực tiếp các chức năng đồng bộ hóa và nếu chức năng đồng bộ hóa đó chặn, thì chức năng không đồng bộ gọi nó cũng vậy

Hãy xem một ví dụ đơn giản về cách chuyển đổi đồng bộ hóa thành không đồng bộ có thể hoạt động. Đây là chương trình “trước” của chúng tôi

def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

def do_stuff():
    a_function()
    another_function()

def main():
    for _ in range(3):
        do_stuff()

main()

Nếu chúng tôi muốn ba phiên bản của

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
0 chạy dưới dạng tác vụ không đồng bộ, thì chúng tôi cần chuyển
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
0 (và có thể là mọi thứ mà nó chạm vào) thành mã không đồng bộ. Đây là lần đầu tiên vượt qua khi chuyển đổi

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())

Lưu ý những thay đổi chúng tôi đã thực hiện đối với 

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
2. Bây giờ,
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
2 sử dụng
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
4 để khởi chạy từng phiên bản của
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
0 như một tác vụ đồng thời, sau đó chờ kết quả (_______16). Chúng tôi cũng đã chuyển đổi
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
7 thành một chức năng không đồng bộ, vì chúng tôi muốn tất cả các phiên bản của
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
7 chạy song song và cùng với bất kỳ chức năng nào khác cần hành vi không đồng bộ

Nếu chúng tôi muốn tiến thêm một bước, chúng tôi cũng có thể chuyển đổi

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
9 thành không đồng bộ

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
2

Tuy nhiên, việc tạo 

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
9 không đồng bộ sẽ là quá mức cần thiết, vì (như chúng tôi đã lưu ý) nó không làm bất cứ điều gì có thể cản trở tiến trình của chương trình của chúng tôi. Ngoài ra, nếu bất kỳ phần đồng bộ nào trong chương trình của chúng tôi được gọi là
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
9, thì chúng tôi cũng phải chuyển đổi chúng thành không đồng bộ, điều này có thể khiến chương trình của chúng tôi trở nên phức tạp hơn mức cần thiết

Bước 3. Kiểm tra kỹ chương trình không đồng bộ Python của bạn

Bất kỳ chương trình được chuyển đổi không đồng bộ nào cũng cần được kiểm tra trước khi đưa vào sản xuất để đảm bảo chương trình hoạt động như mong đợi

Nếu chương trình của bạn có kích thước vừa phải — chẳng hạn, khoảng vài chục dòng — và không cần bộ kiểm tra đầy đủ, thì sẽ không khó để xác minh rằng chương trình đó hoạt động như dự định. Điều đó nói rằng, nếu bạn đang chuyển đổi chương trình thành không đồng bộ như một phần của dự án lớn hơn, trong đó bộ thử nghiệm là vật cố định tiêu chuẩn, bạn nên viết các bài kiểm tra đơn vị cho các thành phần không đồng bộ và đồng bộ hóa.

Cả hai khung thử nghiệm chính trong Python hiện đều có một số loại hỗ trợ không đồng bộ. Khung 

import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
22 riêng của Python bao gồm các đối tượng trường hợp thử nghiệm cho các chức năng không đồng bộ và
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
23 cung cấp 
import asyncio

async def a_function():
    # some async-compatible action that takes a while

def another_function():
    # some sync function, but not a blocking one

async def do_stuff():
    await a_function()
    another_function()

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(do_stuff()))
    await asyncio.gather(tasks)

asyncio.run(main())
24 cho cùng mục đích

Cuối cùng, khi viết bài kiểm tra cho các thành phần không đồng bộ, bạn sẽ cần xử lý tính không đồng bộ của chúng như một điều kiện của bài kiểm tra. Chẳng hạn, không có gì đảm bảo rằng các tác vụ không đồng bộ sẽ hoàn thành theo thứ tự chúng được gửi. Cái đầu tiên có thể đến cuối cùng, và một số có thể không bao giờ hoàn thành. Bất kỳ thử nghiệm nào bạn thiết kế cho chức năng không đồng bộ đều phải tính đến các khả năng này

Cách làm được nhiều hơn với Python

  • Bắt đầu với async trong Python
  • Cách sử dụng asyncio trong Python
  • Cách sử dụng PyInstaller để tạo các tệp thực thi Python
  • hướng dẫn Cython. Cách tăng tốc Python
  • Cách cài đặt Python một cách thông minh
  • Cách quản lý dự án Python với Thơ
  • Cách quản lý dự án Python với Pipenv
  • Virtualenv và venv. Giải thích môi trường ảo Python
  • Python virtualenv và venv nên và không nên làm
  • Giải thích luồng Python và các quy trình con
  • Cách sử dụng trình gỡ lỗi Python
  • Cách sử dụng timeit để cấu hình mã Python
  • Cách sử dụng cProfile để cấu hình mã Python
  • Cách chuyển đổi Python sang JavaScript (và ngược lại)

Có liên quan

  • con trăn
  • Phát triển phần mềm

Serdar Yegulalp là một nhà văn cao cấp tại InfoWorld, tập trung vào học máy, container hóa, devops, hệ sinh thái Python và đánh giá định kỳ

Khi nào tôi nên sử dụng async?

Nếu bạn có bất kỳ nhu cầu nào liên quan đến I/O (chẳng hạn như yêu cầu dữ liệu từ mạng, truy cập cơ sở dữ liệu hoặc đọc và ghi vào hệ thống tệp) , bạn sẽ . Bạn cũng có thể có mã giới hạn CPU, chẳng hạn như thực hiện một phép tính tốn kém, đây cũng là một tình huống tốt để viết mã không đồng bộ.

Điểm không đồng bộ đang chờ là gì?

Ghi chú. Mục đích của async / await là để đơn giản hóa cú pháp cần thiết để sử dụng các API dựa trên lời hứa . Hành vi của async/await tương tự như việc kết hợp các trình tạo và lời hứa. Các chức năng không đồng bộ luôn trả lại một lời hứa.

Một lợi ích của việc sử dụng async đang chờ là gì?

Các công cụ sửa đổi không đồng bộ và đang chờ của NET framework làm cho việc chuyển đổi mã của bạn từ lập trình đồng bộ sang lập trình không đồng bộ trở nên đơn giản và dễ dàng . Do đó, bạn có thể gọi một phương thức không đồng bộ một cách độc lập bằng cách truyền cho nó một đối tượng tác vụ, sau đó thực hiện một số công việc không liên quan, rồi đợi tác vụ đó xem liệu nó đã hoàn thành chưa.

Có bắt buộc phải sử dụng chờ đợi với async không?

Chức năng bạn sử dụng sau từ khóa chờ đợi có thể hoặc không phải là chức năng không đồng bộ. Không có quy tắc bắt buộc rằng nó phải là một chức năng không đồng bộ. Hãy hiểu nó với các ví dụ sau, Tạo một hàm không đồng bộ trả về thông báo đồng bộ, chẳng hạn, Xin chào