Asyncio thu thập Python

Đã trình bày các khái niệm cơ bản trong Python Asyncio Phần 1 – Các khái niệm và mẫu cơ bản, trong phần này của loạt bài, tôi sẽ đi sâu hơn vào cú pháp thực tế được sử dụng khi sử dụng thư viện này trong mã Python. Nhiều ví dụ được sử dụng ở đây dựa trên mã mà chúng tôi đã thực sự sử dụng như một phần của dự án cloudfit của BBC R&D

Viết mã không đồng bộ

Công cụ cơ bản nhất trong bộ công cụ của một lập trình viên bất đồng bộ trong Python là từ khóa mới

def example_function(a, b, c):
  ...
6, được sử dụng để khai báo một hàm coroutine không đồng bộ giống như cách mà
def example_function(a, b, c):
  ...
7 được sử dụng để định nghĩa một hàm đồng bộ bình thường

THUẬT NGỮ. Trong bài viết này, tôi sẽ đề cập đến

def example_function(a, b, c):
  ...
6 làm từ khóa và trong các bài viết sau tôi sẽ đề cập đến
def example_function(a, b, c):
  ...
9 và
r = example_function(1, 2, 3)
0 làm từ khóa. Nói đúng ra điều này không đúng. Trên thực tế,
r = example_function(1, 2, 3)
1 là một từ khóa và
def example_function(a, b, c):
  ...
7 cũng vậy, nhưng vì bạn không thể sử dụng
r = example_function(1, 2, 3)
1 một mình, mà chỉ kết hợp với một từ khóa khác, tôi nghĩ sẽ thuận tiện hơn nhiều và ít gây nhầm lẫn hơn khi nghĩ về
def example_function(a, b, c):
  ...
6 như một từ khóa duy nhất có một . Nó chắc chắn hoạt động như một về cách sử dụng ngôn ngữ

Ví dụ

async def example_coroutine_function(a, b):
    # Asynchronous code goes here
    ...

def example_function(a, b):
    # Synchronous code goes here
    ...

Trong ví dụ trên, chúng tôi định nghĩa một chức năng coroutine

r = example_function(1, 2, 3)
5 và một chức năng thông thường
r = example_function(1, 2, 3)
6. Khối mã tạo thành phần nội dung của định nghĩa hơi khác nhau trong hai trường hợp. Khối mã cho
r = example_function(1, 2, 3)
6 là Python đồng bộ thông thường, trong khi khối mã cho
r = example_function(1, 2, 3)
5 là Python không đồng bộ

QUAN TRỌNG

  • Mã Python không đồng bộ chỉ có thể được đưa vào bên trong một ngữ cảnh phù hợp cho phép nó, điều này hầu như luôn có nghĩa là bên trong một hàm coroutine được xác định bằng cách sử dụng
    def example_function(a, b, c):
      ...
    
    6. Có một bối cảnh khác cho phép mã không đồng bộ mà chúng tôi sẽ đề cập trong bài viết tiếp theo
  • Mã Python không đồng bộ có thể sử dụng bất kỳ từ khóa Python, cấu trúc, v.v. nào được phép trong Python thông thường. Không có gì là không được phép (mặc dù một số điều có thể không được khuyến khích, xem sau)
  • Có một số từ khóa mới chỉ có thể được sử dụng bên trong mã không đồng bộ.
    async def example_coroutine_function(a, b, c):
      ...
    
    0,
    r = example_function(1, 2, 3)
    
    0 và
    def example_function(a, b, c):
      ...
    
    9
  • Lưu ý rằng
    def example_function(a, b, c):
      ...
    
    6 không phải là một trong những từ khóa được dành riêng để sử dụng trong mã không đồng bộ. Nó có thể được sử dụng ở bất cứ đâu mà
    def example_function(a, b, c):
      ...
    
    7 có thể được sử dụng, mặc dù tác dụng của nó hơi khác một chút

Việc khai báo một hàm coroutine bằng cách sử dụng

def example_function(a, b, c):
  ...
6 trông có vẻ tương tự như việc khai báo một hàm thông thường bằng cách sử dụng
def example_function(a, b, c):
  ...
7. Hầu hết thời gian viết một cái khá giống nhau, tuy nhiên có một số điểm khác biệt chính, điều này rất quan trọng đối với lập trình không đồng bộ

  • Từ khóa
    def example_function(a, b, c):
      ...
    
    7 của Python tạo một đối tượng có thể gọi được với một tên, khi đối tượng được gọi, khối mã của hàm sẽ được chạy. Ví dụ

    def example_function(a, b, c):
      ...
    

    có nghĩa là

    r = example_function(1, 2, 3)
    
    6 hiện là một đối tượng có thể gọi được, có ba tham số. Khi bạn gọi nó như vậy

    r = example_function(1, 2, 3)
    

    điều này khiến mã chức năng được chạy ngay lập tức dưới dạng lệnh gọi chương trình con và giá trị trả về của nó được gán cho

    async def example_coroutine_function(a, b, c):
      ...
    
    9

  • Từ khóa Python
    def example_function(a, b, c):
      ...
    
    6 tạo một đối tượng có thể gọi được với một tên, khi đối tượng được gọi, khối mã của hàm không được chạy. Ví dụ

    async def example_coroutine_function(a, b, c):
      ...
    

    có nghĩa là

    r = example_function(1, 2, 3)
    
    5 hiện là một đối tượng có thể gọi được, có ba tham số. Khi bạn gọi nó như vậy

    r = example_coroutine_function(1, 2, 3)
    

    điều này không làm cho khối mã chức năng được chạy. Thay vào đó, một đối tượng của lớp

    r = example_coroutine_function(1, 2, 3)
    
    2 được tạo và được gán cho
    async def example_coroutine_function(a, b, c):
      ...
    
    9. Để làm cho khối mã thực sự chạy, bạn cần sử dụng một trong những phương tiện mà asyncio cung cấp để chạy coroutine. Thông thường nhất đây là từ khóa
    async def example_coroutine_function(a, b, c):
      ...
    
    0. Hàm
    r = example_coroutine_function(1, 2, 3)
    
    5 được sử dụng trong một Các ví dụ khác có thể được tìm thấy trong. Xem ví dụ
    r = example_coroutine_function(1, 2, 3)
    
    6

THUẬT NGỮ. Việc mọi người cẩu thả trong thuật ngữ của họ và sử dụng từ “coroutine” để chỉ bất kỳ điều nào trong ba điều là điều khá phổ biến.

  • Khối mã của mã không đồng bộ bên trong câu lệnh
    def example_function(a, b, c):
      ...
    
    6
  • Đối tượng có thể gọi được mà câu lệnh
    def example_function(a, b, c):
      ...
    
    6 tạo ra
  • Đối tượng của lớp
    r = example_coroutine_function(1, 2, 3)
    
    2 được trả về bởi đối tượng có thể gọi được khi nó được gọi

Trong loạt bài này, tôi sẽ cố gắng làm rõ tôi đang nói về vấn đề nào trong số này tại bất kỳ điểm cụ thể nào. Cụ thể, tôi thường sẽ nói “đối tượng coroutine” cho một đối tượng thuộc lớp

r = example_coroutine_function(1, 2, 3)
2 và “hàm coroutine” cho đối tượng có thể gọi trả về nó. Khi tôi cần đề cập cụ thể đến khối mã (không thường xuyên), tôi sẽ gọi nó là “khối mã bên trong câu lệnh
def example_function(a, b, c):
  ...
6 xác định chức năng coroutine”

GHI CHÚ Gõ. Nếu bạn đang sử dụng thư viện

async def example_coroutine_function(a: A, b: B) -> C:
  ...
2 thì việc khai báo các hàm coroutine đôi khi có thể hơi khó hiểu

async def example_coroutine_function(a: A, b: B) -> C:
  ...

định nghĩa

r = example_function(1, 2, 3)
5 là một hàm có thể gọi được nhận hai tham số loại A và B và trả về một đối tượng loại
async def example_coroutine_function(a: A, b: B) -> C:
  ...
4. Rất hiếm khi bạn cần tham khảo rõ ràng loại trả về này

Nếu bạn tò mò về hai tham số loại

async def example_coroutine_function(a: A, b: B) -> C:
  ...
5 trong định nghĩa trên thì chúng có liên quan đến cách thức hoạt động của vòng lặp sự kiện. Tham số loại đầu tiên thực sự biểu thị loại giá trị mà quy trình đăng ký sẽ chuyển đến vòng lặp sự kiện bất cứ khi nào nó mang lại, trong khi tham số thứ hai biểu thị loại giá trị mà vòng lặp sự kiện sẽ chuyển trở lại quy trình đăng ký bất cứ khi nào nó được đánh thức lại. Trong thực tế, các loại thực tế của các đối tượng này được xác định bởi bộ máy nội bộ của quá trình triển khai vòng lặp sự kiện và không bao giờ cần được đề cập rõ ràng trong mã máy khách trừ khi bạn đang viết triển khai vòng lặp sự kiện của riêng mình (đây là một chủ đề khá nâng cao ngoài

Các async def example_coroutine_function(a, b, c): ... 0 từ khóa và Awaitables

Một trong những từ khóa mới được thêm vào ngôn ngữ để hỗ trợ asyncio là

async def example_coroutine_function(a, b, c):
  ...
0. Theo nhiều cách, từ khóa này là cốt lõi của mã không đồng bộ. Nó chỉ có thể được sử dụng bên trong các khối mã không đồng bộ (nghĩa là. trong khối mã của câu lệnh
def example_function(a, b, c):
  ...
6 xác định hàm coroutine), và nó được sử dụng như một biểu thức nhận một tham số duy nhất và trả về một giá trị

Ví dụ

là một câu lệnh Python hợp lệ sẽ thực hiện hành động

async def example_coroutine_function(a, b, c):
  ...
0 trên đối tượng
import asyncio

async def get_some_values_from_io():
    # Some IO code which returns a list of values
    ...

vals = []

async def fetcher():
    while True:
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print (len(vals))

        await asyncio.sleep(1)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())
0 và trả về một giá trị sẽ được gán cho
async def example_coroutine_function(a, b, c):
  ...
9. Chính xác điều gì sẽ xảy ra khi câu lệnh
async def example_coroutine_function(a, b, c):
  ...
0 này được thực thi sẽ phụ thuộc vào đối tượng
import asyncio

async def get_some_values_from_io():
    # Some IO code which returns a list of values
    ...

vals = []

async def fetcher():
    while True:
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print (len(vals))

        await asyncio.sleep(1)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())
0 là gì

Một đối tượng coroutine là “awaitable” (nó có thể được sử dụng trong câu lệnh

async def example_coroutine_function(a, b, c):
  ...
0). Nhớ lại rằng khi bạn đang thực thi mã không đồng bộ, bạn luôn làm như vậy trong ngữ cảnh của một “Tác vụ”, là một đối tượng được duy trì bởi Vòng lặp sự kiện và mỗi Tác vụ có ngăn xếp cuộc gọi riêng. Lần đầu tiên một đối tượng Coroutine được chờ đợi, khối mã bên trong định nghĩa của nó được thực thi trong Tác vụ hiện tại, với ngữ cảnh mã mới của nó được thêm vào đầu ngăn xếp lệnh gọi cho Tác vụ này, giống như một lệnh gọi hàm bình thường. Khi khối mã kết thúc (hoặc nếu không thì trả về) thì quá trình thực thi sẽ quay trở lại câu lệnh chờ đã gọi nó. Giá trị trả về của câu lệnh
async def example_coroutine_function(a, b, c):
  ...
0 là giá trị được trả về bởi khối mã. Nếu một đối tượng Coroutine được đợi lần thứ hai, điều này sẽ tạo ra một ngoại lệ. Theo cách này, bạn có thể nghĩ về việc đợi một đối tượng Coroutine giống như gọi một hàm, với điểm khác biệt đáng chú ý là khối mã của đối tượng Coroutine có thể chứa mã không đồng bộ và do đó có thể tạm dừng tác vụ hiện tại trong khi chạy, mà khối mã của hàm

Trên thực tế, có ba loại đối tượng có thể chờ đợi

  • Một đối tượng Coroutine. Khi được chờ đợi, nó sẽ thực thi khối mã của quy trình đăng ký trong Tác vụ hiện tại. Câu lệnh
    async def example_coroutine_function(a, b, c):
      ...
    
    0 sẽ trả về giá trị được trả về bởi khối mã
  • Bất kỳ đối tượng nào của lớp
    import asyncio
    
    async def get_some_values_from_io():
        # Some IO code which returns a list of values
        ...
    
    vals = []
    
    async def fetcher():
        while True:
            io_vals = await get_some_values_from_io()
    
            for val in io_vals:
                vals.append(io_vals)
    
    async def monitor():
        while True:
            print (len(vals))
    
            await asyncio.sleep(1)
    
    async def main():
        t1 = asyncio.create_task(fetcher())
        t2 = asyncio.create_task(monitor())
        await asyncio.gather(t1, t2)
    
    asyncio.run(main())
    
    7 mà khi được chờ đợi sẽ khiến Tác vụ hiện tại bị tạm dừng cho đến khi một điều kiện cụ thể xảy ra (xem phần tiếp theo)
  • Một đối tượng thực hiện phương thức ma thuật
    import asyncio
    
    async def get_some_values_from_io():
        # Some IO code which returns a list of values
        ...
    
    vals = []
    
    async def fetcher():
        while True:
            io_vals = await get_some_values_from_io()
    
            for val in io_vals:
                vals.append(io_vals)
    
    async def monitor():
        while True:
            print (len(vals))
    
            await asyncio.sleep(1)
    
    async def main():
        t1 = asyncio.create_task(fetcher())
        t2 = asyncio.create_task(monitor())
        await asyncio.gather(t1, t2)
    
    asyncio.run(main())
    
    8, trong trường hợp đó, điều gì xảy ra khi nó được chờ đợi được xác định bởi phương thức đó

Cái cuối cùng ở đó để những người viết thư viện có thể tạo các lớp đối tượng mới của riêng họ có thể chờ đợi và làm điều gì đó đặc biệt khi được chờ đợi. Thông thường, bạn nên làm cho các đối tượng có thể chờ đợi tùy chỉnh của mình hoạt động giống như một đối tượng Coroutine hoặc giống như một đối tượng Tương lai và ghi lại trong các chuỗi tài liệu của lớp. Tạo các lớp có thể chờ tùy chỉnh như thế này là một chủ đề nâng cao hơn một chút, mặc dù một chủ đề có thể xuất hiện khi viết trình bao bọc asyncio cho các thư viện io đồng bộ chẳng hạn

GHI CHÚ Gõ. Nếu bạn đang sử dụng

async def example_coroutine_function(a: A, b: B) -> C:
  ...
2 thì có một lớp trừu tượng
f = asyncio.get_running_loop().create_future()
0 là lớp chung chung, do đó,
f = asyncio.get_running_loop().create_future()
1 đối với một số loại
f = asyncio.get_running_loop().create_future()
2 có nghĩa là “bất kỳ thứ gì có thể chờ đợi và khi được sử dụng trong câu lệnh
async def example_coroutine_function(a, b, c):
  ...
0 sẽ trả về một thứ thuộc loại
f = asyncio.get_running_loop().create_future()
2”

Một trong những điểm quan trọng nhất cần hiểu là Tác vụ hiện đang thực thi không thể bị tạm dừng bằng bất kỳ cách nào khác ngoài việc chờ đợi một tương lai (hoặc một đối tượng có thể chờ đợi tùy chỉnh hoạt động như một). Và đó là điều chỉ có thể xảy ra bên trong mã không đồng bộ. Vì vậy, bất kỳ câu lệnh

async def example_coroutine_function(a, b, c):
  ...
0 nào cũng có thể khiến tác vụ hiện tại của bạn tạm dừng, nhưng không đảm bảo sẽ. Ngược lại, bất kỳ câu lệnh nào không phải là câu lệnh
async def example_coroutine_function(a, b, c):
  ...
0 (hoặc
def example_function(a, b, c):
  ...
9 hoặc
r = example_function(1, 2, 3)
0 trong một số trường hợp nhất định sẽ được giải thích trong bài đăng tiếp theo) không thể khiến Nhiệm vụ hiện tại của bạn bị tạm dừng

Điều này có nghĩa là các vấn đề về mã đa luồng truyền thống của các cuộc chạy đua dữ liệu trong đó các luồng thực thi khác nhau đều thay đổi cùng một giá trị sẽ bị giảm nghiêm trọng trong mã không đồng bộ, nhưng không bị loại bỏ hoàn toàn. Đặc biệt đối với mục đích chia sẻ dữ liệu giữa các Tác vụ trên cùng một vòng lặp sự kiện, tất cả mã đồng bộ có thể được coi là "nguyên tử". Để minh họa điều này có nghĩa là gì, hãy xem xét đoạn mã sau

import asyncio

async def get_some_values_from_io():
    # Some IO code which returns a list of values
    ...

vals = []

async def fetcher():
    while True:
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print (len(vals))

        await asyncio.sleep(1)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())

thì mặc dù cả

f = asyncio.get_running_loop().create_future()
9 và
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
0 đều truy cập vào biến toàn cục
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
1 nhưng chúng vẫn làm như vậy trong hai tác vụ đang chạy trong cùng một vòng lặp sự kiện. Vì lý do này, câu lệnh in trong
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
0 không thể chạy trừ khi
f = asyncio.get_running_loop().create_future()
9 hiện đang ngủ chờ io. Điều này có nghĩa là không thể in chiều dài của
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
1 trong khi vòng lặp
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
5 chỉ chạy được một phần. Vì vậy, nếu
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
6 luôn trả về 10 giá trị cùng một lúc (ví dụ) thì độ dài được in của
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
1 sẽ luôn là bội số của mười. Đơn giản là câu lệnh in không thể thực thi tại thời điểm mà
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
1 có độ dài không phải là bội số của mười

Mặt khác, nếu có một câu lệnh

async def example_coroutine_function(a, b, c):
  ...
0 bên trong vòng lặp
async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())
5 thì điều này sẽ không còn được đảm bảo nữa

GHI CHÚ. Lưu ý rằng các cuộc gọi

t = asyncio.get_event_loop().create_task(example_coroutine_function())
1 ở trên là dư thừa. Cơ thể của
t = asyncio.get_event_loop().create_task(example_coroutine_function())
2 có thể được rút gọn thành
t = asyncio.get_event_loop().create_task(example_coroutine_function())
3

Một đối tượng

t = asyncio.get_event_loop().create_task(example_coroutine_function())
4 là một loại có thể chờ đợi. Không giống như một đối tượng coroutine khi một tương lai được chờ đợi, nó không khiến một khối mã được thực thi. Thay vào đó, một đối tượng trong tương lai có thể được coi là đại diện cho một số quá trình đang diễn ra ở nơi khác và có thể chưa kết thúc hoặc chưa kết thúc.

Khi bạn chờ đợi một tương lai, điều sau đây sẽ xảy ra

  • Nếu quá trình mà tương lai đại diện đã kết thúc và trả về một giá trị thì câu lệnh chờ đợi ngay lập tức trả về giá trị đó
  • Nếu quá trình mà tương lai đại diện đã kết thúc và đưa ra một ngoại lệ thì câu lệnh chờ đợi ngay lập tức đưa ra ngoại lệ đó
  • Nếu quá trình mà tương lai đại diện chưa kết thúc thì Tác vụ hiện tại sẽ bị tạm dừng cho đến khi quá trình kết thúc. Sau khi hoàn tất, nó sẽ hoạt động như được mô tả trong hai gạch đầu dòng đầu tiên tại đây

Tất cả các đối tượng Tương lai

t = asyncio.get_event_loop().create_task(example_coroutine_function())
5 đều có giao diện đồng bộ sau ngoài việc có thể chờ đợi

  • t = asyncio.get_event_loop().create_task(example_coroutine_function())
    
    6 trả về
    t = asyncio.get_event_loop().create_task(example_coroutine_function())
    
    7 nếu quá trình mà tương lai đại diện đã kết thúc
  • t = asyncio.get_event_loop().create_task(example_coroutine_function())
    
    8 đưa ra một ngoại lệ
    t = asyncio.get_event_loop().create_task(example_coroutine_function())
    
    9 nếu quá trình chưa kết thúc. Nếu quá trình kết thúc, nó trả về ngoại lệ mà nó đã nêu ra, hoặc
    def example_function(a, b, c):
      ...
    
    00 nếu nó kết thúc mà không đưa ra
  • def example_function(a, b, c):
      ...
    
    01 đưa ra một ngoại lệ
    t = asyncio.get_event_loop().create_task(example_coroutine_function())
    
    9 nếu quá trình chưa kết thúc. Nếu quá trình kết thúc, nó sẽ tăng ngoại lệ mà nó đã nêu hoặc trả về giá trị mà nó đã trả về nếu quá trình kết thúc mà không tăng

Điều quan trọng cần lưu ý là không có cách nào để một tương lai đã được thực hiện có thể quay trở lại thành một tương lai chưa được thực hiện. Một tương lai trở nên được thực hiện là một sự xuất hiện một lần

QUAN TRỌNG. Sự khác biệt giữa Coroutine và Future rất quan trọng. Mã của Coroutine sẽ không được thực thi cho đến khi nó được chờ đợi. Một tương lai đại diện cho một cái gì đó vẫn đang thực thi và chỉ cần cho phép mã của bạn đợi nó hoàn thành, kiểm tra xem nó đã hoàn thành chưa và tìm nạp kết quả nếu nó đã hoàn thành.

QUAN TRỌNG. Các đối tượng thực hiện phương pháp ma thuật

import asyncio

async def get_some_values_from_io():
    # Some IO code which returns a list of values
    ...

vals = []

async def fetcher():
    while True:
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print (len(vals))

        await asyncio.sleep(1)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())
8 có thể làm hầu hết mọi thứ khi được chờ đợi. Chúng có thể hoạt động giống Coroutines hơn hoặc giống Futures hơn. Họ có thể làm một cái gì đó hoàn toàn khác. Tài liệu cho lớp được đề cập thường phải làm rõ hành vi của họ là gì

Bạn có thể sẽ không thường xuyên tạo tương lai của riêng mình trừ khi bạn đang triển khai các thư viện mới mở rộng asyncio. Tuy nhiên, bạn sẽ thấy rằng các hàm thư viện thường trả về tương lai. Nếu bạn cần trực tiếp tạo ra tương lai của chính mình, bạn có thể thực hiện điều đó bằng cách gọi tới

f = asyncio.get_running_loop().create_future()

Mặt khác, bạn có thể sẽ thấy rằng bạn sử dụng một phương pháp liên quan,

t = asyncio.get_event_loop().create_task(example_coroutine_function())
1 khá thường xuyên…

GHI CHÚ Gõ. Nếu bạn muốn chỉ định rằng một biến là Tương lai thì bạn có thể sử dụng lớp

import asyncio

async def get_some_values_from_io():
    # Some IO code which returns a list of values
    ...

vals = []

async def fetcher():
    while True:
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print (len(vals))

        await asyncio.sleep(1)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())
7 làm chú thích kiểu. Nếu bạn muốn chỉ định rằng kết quả của Tương lai phải thuộc một loại cụ thể,
f = asyncio.get_running_loop().create_future()
2 thì bạn có thể sử dụng ký hiệu sau

(bằng Python 3. 6, bạn sẽ cần đặt

def example_function(a, b, c):
  ...
07 trong dấu ngoặc kép để điều này hoạt động chính xác, nhưng trong các phiên bản Python sau này, điều này không còn cần thiết nữa)

Như đã mô tả trong bài viết trước, mỗi vòng lặp sự kiện chứa một số tác vụ và mọi coroutine đang thực thi đều thực hiện như vậy bên trong một tác vụ. Vì vậy, câu hỏi làm thế nào để tạo một nhiệm vụ có vẻ như là một câu hỏi quan trọng

Tạo một tác vụ là một vấn đề đơn giản và có thể được thực hiện hoàn toàn bằng mã đồng bộ

async def example_coroutine_function():
    ...

t = asyncio.create_task(example_coroutine_function())

GHI CHÚ. Trong Trăn 3. 6 chức năng

def example_function(a, b, c):
  ...
08 không khả dụng, nhưng bạn vẫn có thể tạo tác vụ bằng cách sử dụng

t = asyncio.get_event_loop().create_task(example_coroutine_function())

điều này hoàn toàn giống nhau, nhưng dài dòng hơn một chút

Phương thức

t = asyncio.get_event_loop().create_task(example_coroutine_function())
1 lấy một đối tượng coroutine làm tham số và trả về một đối tượng
def example_function(a, b, c):
  ...
10, kế thừa từ
import asyncio

async def get_some_values_from_io():
    # Some IO code which returns a list of values
    ...

vals = []

async def fetcher():
    while True:
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print (len(vals))

        await asyncio.sleep(1)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())
7. Cuộc gọi tạo tác vụ bên trong vòng lặp sự kiện cho luồng hiện tại và bắt đầu thực thi tác vụ ở đầu khối mã của coroutine. Tương lai được trả về sẽ được đánh dấu là
def example_function(a, b, c):
  ...
12 chỉ khi tác vụ đã hoàn thành. Như bạn có thể mong đợi, giá trị trả về của khối mã của coroutine là
def example_function(a, b, c):
  ...
13 sẽ được lưu trữ trong đối tượng tương lai khi nó kết thúc (và nếu nó tăng lên thì ngoại lệ sẽ bị bắt và lưu trữ trong tương lai)

Tạo một tác vụ để bọc một coroutine là một lệnh gọi đồng bộ, vì vậy nó có thể được thực hiện ở bất cứ đâu, kể cả bên trong mã đồng bộ hoặc không đồng bộ. Nếu bạn làm điều đó bằng mã không đồng bộ thì vòng lặp sự kiện đã chạy (vì nó hiện đang thực thi mã không đồng bộ của bạn) và khi nó có cơ hội tiếp theo (tức là. lần tới khi tác vụ hiện tại của bạn tạm dừng) nó có thể làm cho tác vụ mới hoạt động

Tuy nhiên, khi bạn thực hiện bằng mã đồng bộ, rất có thể vòng lặp sự kiện chưa chạy. Thao tác thủ công các vòng lặp sự kiện không được khuyến khích bởi tài liệu python. Trừ khi bạn đang phát triển các thư viện mở rộng chức năng

def example_function(a, b, c):
  ...
14, có lẽ bạn nên tránh cố gắng tạo một tác vụ từ mã đồng bộ

Nếu bạn cần gọi một đoạn mã không đồng bộ trong một tập lệnh đồng bộ khác, bạn có thể sử dụng

def example_function(a, b, c):
  ...
15

Chạy các chương trình không đồng bộ

Với sự ra đời của

def example_function(a, b, c):
  ...
15 trong Python 3. 7 và việc loại bỏ tham số
def example_function(a, b, c):
  ...
17 khỏi nhiều hàm asyncio trong Python 3. 10, quản lý các vòng lặp sự kiện là điều mà bạn khó có thể gặp phải, trừ khi bạn đang phát triển một thư viện không đồng bộ. Các đối tượng vòng lặp sự kiện vẫn ở đó và có thể truy cập được. Có cả một trang trong tài liệu thảo luận về chúng. Nếu bạn đang làm việc trong Python 3. 7 hoặc cao hơn, vui mừng và cảm ơn vì
def example_function(a, b, c):
  ...
15

def example_function(a, b, c):
  ...
19 sẽ chạy
def example_function(a, b, c):
  ...
20 và trả về kết quả. Nó sẽ luôn bắt đầu một vòng lặp sự kiện mới và không thể gọi nó khi vòng lặp sự kiện đang chạy. Điều này dẫn đến một số cách rõ ràng để chạy mã không đồng bộ của bạn

Đầu tiên là có mọi thứ trong các coroutine không đồng bộ và có một chức năng nhập rất đơn giản

def example_function(a, b, c):
  ...
0

Cách thứ hai là bọc từng lệnh gọi coroutine trong một lệnh

def example_function(a, b, c):
  ...
21 riêng biệt. Lưu ý rằng điều này bỏ qua tất cả các lợi ích của asyncio. Tuy nhiên, có thể có kịch bản kỳ lạ mà đây là điều đúng đắn để làm

def example_function(a, b, c):
  ...
1

Lưu ý rằng những ví dụ đơn giản này không sử dụng khả năng của mã không đồng bộ để hoạt động đồng thời trên nhiều tác vụ. Một ví dụ hợp lý hơn được đưa ra tại. Khi bạn làm việc với

def example_function(a, b, c):
  ...
14 trong python, bạn sẽ tìm hiểu về những cách phức tạp hơn để quản lý công việc của mình, nhưng điều này là đủ để bạn bắt đầu

Tương tác vòng lặp sự kiện thủ công

Nếu bạn đang sử dụng Python 3. 6, và bạn cần chạy coroutines từ mã đồng bộ hóa thông thường (mà bạn có thể sẽ làm, nếu bạn muốn bắt đầu một thứ gì đó. ) thì bạn sẽ cần bắt đầu vòng lặp sự kiện. Có hai phương pháp để làm điều này

def example_function(a, b, c):
  ...
2

sẽ khiến vòng lặp sự kiện chạy mãi mãi (hoặc cho đến khi bị giết rõ ràng). Điều này thường không đặc biệt hữu ích. Hữu ích hơn nhiều là

def example_function(a, b, c):
  ...
3

trong đó có một tham số duy nhất. Nếu tham số là một tương lai (chẳng hạn như một tác vụ) thì vòng lặp sẽ được chạy cho đến khi hoàn thành tương lai, trả về kết quả của nó hoặc đưa ra ngoại lệ của nó. Vì vậy, đặt nó lại với nhau

def example_function(a, b, c):
  ...
4

sẽ tạo một tác vụ mới thực thi

r = example_function(1, 2, 3)
5 bên trong vòng lặp sự kiện cho đến khi hoàn thành, sau đó trả về kết quả

Trên thực tế, điều này có thể được đơn giản hóa hơn nữa vì nếu bạn chuyển một đối tượng coroutine làm tham số cho

def example_function(a, b, c):
  ...
24 thì nó sẽ tự động gọi
t = asyncio.get_event_loop().create_task(example_coroutine_function())
1 cho bạn

Làm thế nào để nhường quyền kiểm soát

Không có lệnh đơn giản nào để mang lại quyền kiểm soát cho vòng lặp sự kiện để các tác vụ khác có thể chạy. Trong hầu hết các trường hợp trong chương trình asyncio, đây không phải là điều bạn muốn thực hiện một cách rõ ràng, ưu tiên cho phép tự động kiểm soát khi bạn chờ đợi một tương lai được trả về bởi một số thư viện cơ bản xử lý một số loại IO

Tuy nhiên, đôi khi bạn cần, và đặc biệt nó khá hữu ích trong quá trình thử nghiệm và gỡ lỗi. Kết quả là có một thành ngữ được công nhận để làm điều này nếu bạn cần. tuyên bố

sẽ tạm dừng tác vụ hiện tại và cho phép các tác vụ khác được thực hiện. Cách thức hoạt động này là sử dụng hàm

def example_function(a, b, c):
  ...
26 được cung cấp bởi thư viện
def example_function(a, b, c):
  ...
14. Hàm này nhận một tham số duy nhất là số giây và trả về một tương lai chưa được đánh dấu là xong nhưng sẽ là khi số giây đã chỉ định trôi qua

Chỉ định số giây bằng 0 có tác dụng làm gián đoạn tác vụ hiện tại nếu các tác vụ khác đang chờ xử lý, nhưng nếu không thì không làm gì cả vì thời gian ngủ bằng không

Việc triển khai

def example_function(a, b, c):
  ...
26 trong thư viện tiêu chuẩn đã được tối ưu hóa để làm cho hoạt động này hiệu quả

Khi sử dụng

def example_function(a, b, c):
  ...
26 với tham số khác 0, điều đáng chú ý là chỉ vì tương lai sẽ hoàn thành khi số giây đã trôi qua không có nghĩa là nhiệm vụ của bạn sẽ luôn hoạt động trở lại vào thời điểm đó. Trên thực tế, nó có thể hoạt động trở lại vào bất kỳ thời điểm nào sau thời điểm đó, vì nó chỉ có thể hoạt động trở lại khi không có tác vụ nào khác đang chạy trên vòng lặp sự kiện

Tóm lược

  • Bạn chỉ có thể sử dụng các từ khóa
    async def example_coroutine_function(a, b, c):
      ...
    
    0,
    r = example_function(1, 2, 3)
    
    0 và
    def example_function(a, b, c):
      ...
    
    9 bên trong mã không đồng bộ
  • Mã không đồng bộ phải được chứa bên trong một khai báo
    def example_function(a, b, c):
      ...
    
    6 (hoặc một nơi khác mà chúng tôi sẽ đề cập trong bài viết tiếp theo), nhưng khai báo có thể đi đến bất cứ nơi nào
    def example_function(a, b, c):
      ...
    
    7 được phép
  • Khi bạn gọi
    async def example_coroutine_function(a, b, c):
      ...
    
    0, bạn phải gọi nó theo một trong những cách sau
    • Đối tượng coroutine, là giá trị trả về của hàm coroutine được xác định bằng cách sử dụng
      def example_function(a, b, c):
        ...
      
      6
      • Mã của coroutine sẽ chỉ được thực thi khi nó được chờ đợi hoặc được bao bọc trong một tác vụ
    • Một đối tượng trong tương lai, đại diện cho một quá trình đang diễn ra ở một nơi khác có thể đã kết thúc
      • Chờ đợi một tương lai sẽ không khiến mã được thực thi, nhưng có thể tạm dừng tác vụ hiện tại của bạn cho đến khi quá trình khác hoàn thành
    • Một đối tượng thực hiện phương pháp ma thuật
      import asyncio
      
      async def get_some_values_from_io():
          # Some IO code which returns a list of values
          ...
      
      vals = []
      
      async def fetcher():
          while True:
              io_vals = await get_some_values_from_io()
      
              for val in io_vals:
                  vals.append(io_vals)
      
      async def monitor():
          while True:
              print (len(vals))
      
              await asyncio.sleep(1)
      
      async def main():
          t1 = asyncio.create_task(fetcher())
          t2 = asyncio.create_task(monitor())
          await asyncio.gather(t1, t2)
      
      asyncio.run(main())
      
      8
      • Điều gì xảy ra sau đó có thể là bất cứ điều gì, hãy kiểm tra tài liệu về đối tượng được đề cập
  • Bạn có thể bọc một coroutine trong một tác vụ để làm cho nó thực thi và trả về một tương lai mà bạn có thể sử dụng để theo dõi kết quả

Asyncio thu thập Python

Làm chương trình thực tế

Vì vậy, kết luận của chúng tôi về cú pháp cơ bản để viết mã không đồng bộ. Chỉ với điều này, bạn đã có thể tạo một chương trình không đồng bộ hoàn toàn tốt, có thể khởi tạo nhiều tác vụ và cho phép hoán đổi chúng vào và ra. Ví dụ sau đây là một chương trình Python hoạt động đầy đủ chỉ sử dụng những thứ có trong bài đăng này

def example_function(a, b, c):
  ...
5

Chương trình này sẽ chạy bốn tác vụ in các số từ 0 đến 99 và sau khi in xong, mỗi tác vụ sẽ nhường quyền điều khiển để cho phép các tác vụ khác tiếp quản. Nó chứng minh rõ ràng rằng asyncio cho phép thực hiện nhiều việc xen kẽ

Để thực sự làm bất cứ điều gì hữu ích, bạn sẽ cần sử dụng một trong các thư viện triển khai io, chẳng hạn như aiohttp, và khi bạn làm như vậy, bạn có thể thấy rằng có một số thứ trong giao diện của chúng mà tôi chưa đề cập đến trong phần này . Cụ thể, bạn có thể thấy rằng giao diện sử dụng

r = example_function(1, 2, 3)
0 và cũng có thể là
def example_function(a, b, c):
  ...
9. Vì vậy, đó sẽ là chủ đề của bài viết tiếp theo trong loạt bài này. Python Asyncio Phần 3 - Trình quản lý ngữ cảnh không đồng bộ và Trình lặp không đồng bộ

Asyncio thu thập Python là gì?

Hàm Gather() của Mô-đun Asyncio trong Python . Đang chờ trên đối tượng Tương lai được trả về sẽ chạy các tệp đang chờ để hoàn thành và trả về kết quả. starts and runs multiple awaitables concurrently, grouping them as a Future. Awaiting on the returned Future object runs the awaitables to completion and returns the results.

asyncio thu thập hoạt động như thế nào?

Không đồng bộ. Chức năng mô-đun thu thập () cho phép người gọi nhóm nhiều đối tượng đang chờ lại với nhau . Sau khi được nhóm lại, các mục đang chờ có thể được thực thi đồng thời, được chờ và bị hủy. Chạy đồng thời các đối tượng có thể chờ trong chuỗi aws.

Thu thập trong Python là gì?

TensorFlow là thư viện Python mã nguồn mở do Google thiết kế để phát triển các mô hình Machine Learning và mạng lưới thần kinh học sâu. thu thập () được sử dụng để cắt thang đo đầu vào dựa trên các chỉ số được cung cấp

Asyncio có thu thập kết quả trả về theo thứ tự không?

Không đồng bộ. thu thập () chạy nhiều hoạt động không đồng bộ và kết thúc một quy trình đăng quang dưới dạng một tác vụ. Không đồng bộ. gather() trả về một bộ kết quả theo cùng một thứ tự chờ đợi .