asyncio.ensure_future so với BaseEventLoop.create_task so với quy trình đơn giản?


96

Tôi đã xem một số hướng dẫn cơ bản về Python 3.5 trên asyncio thực hiện thao tác tương tự với nhiều hương vị khác nhau. Trong mã này:

import asyncio  

async def doit(i):
    print("Start %d" % i)
    await asyncio.sleep(3)
    print("End %d" % i)
    return i

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    #futures = [asyncio.ensure_future(doit(i), loop=loop) for i in range(10)]
    #futures = [loop.create_task(doit(i)) for i in range(10)]
    futures = [doit(i) for i in range(10)]
    result = loop.run_until_complete(asyncio.gather(*futures))
    print(result)

Tất cả ba biến thể ở trên xác định futuresbiến đều đạt được cùng một kết quả; sự khác biệt duy nhất tôi có thể thấy là với biến thể thứ ba, việc thực thi không theo thứ tự (điều này không thành vấn đề trong hầu hết các trường hợp). Có sự khác biệt nào khác không? Có những trường hợp nào mà tôi không thể chỉ sử dụng biến thể đơn giản nhất (danh sách các coroutines đơn giản) không?

Câu trả lời:


116

Thông tin thực tế:

Bắt đầu từ Python 3.7, asyncio.create_task(coro)hàm cấp cao đã được thêm vào cho mục đích này.

Bạn nên sử dụng nó thay vì các cách khác để tạo nhiệm vụ từ thời gian đăng quang. Tuy nhiên, nếu bạn cần tạo tác vụ từ chờ tùy ý, bạn nên sử dụng asyncio.ensure_future(obj).


Thông tin cũ:

ensure_future vs create_task

ensure_futurelà một phương pháp để tạo Tasktừ coroutine. Nó tạo ra các tác vụ theo những cách khác nhau dựa trên đối số (bao gồm cả việc sử dụng create_taskfor coroutines và các đối tượng tương lai).

create_tasklà một phương pháp trừu tượng của AbstractEventLoop. Các vòng lặp sự kiện khác nhau có thể triển khai chức năng này theo những cách khác nhau.

Bạn nên sử dụng ensure_futuređể tạo nhiệm vụ. Bạn create_taskchỉ cần thực hiện loại vòng lặp sự kiện của riêng mình.

Cập nhật:

@ bj0 đã chỉ vào câu trả lời của Guido về chủ đề này:

Vấn đề ensure_future()là nếu bạn có một cái gì đó có thể là một quy trình đăng quang hoặc một Future(cái sau bao gồm một Taskvì đó là một lớp con của Future) và bạn muốn có thể gọi một phương thức trên đó chỉ được xác định trên Future(có thể là về ví dụ hữu ích đang cancel()). Khi nó đã là một Future(hoặc Task) điều này không làm gì cả; khi nó là một quy trình, nó sẽ bao bọc nó trong một Task.

Nếu bạn biết rằng bạn có một quy trình đăng ký và bạn muốn nó được lên lịch, thì API chính xác để sử dụng là create_task(). Thời điểm duy nhất khi bạn nên gọi ensure_future()là khi bạn đang cung cấp một API (giống như hầu hết các API riêng của asyncio) chấp nhận một quy trình đăng ký hoặc một quy trình đăng ký Futurevà bạn cần phải làm gì đó với nó mà yêu cầu bạn phải có Future.

và sau đó:

Cuối cùng, tôi vẫn tin rằng đó ensure_future()là một cái tên ít người biết đến cho một phần chức năng hiếm khi cần thiết. Khi tạo một nhiệm vụ từ một quy trình đăng ký, bạn nên sử dụng tên thích hợp loop.create_task(). Có lẽ nên có một bí danh cho điều đó asyncio.create_task()?

Thật là ngạc nhiên đối với tôi. Động lực chính của tôi khi sử dụng ensure_futuretất cả là chức năng cấp cao hơn của nó so với thành viên của vòng lặp create_task(thảo luận chứa một số ý tưởng như thêm asyncio.spawnhoặc asyncio.create_task).

Tôi cũng có thể chỉ ra rằng theo ý kiến ​​của tôi, khá tiện lợi khi sử dụng hàm phổ quát có thể xử lý bất kỳ Awaitablethay vì chỉ coroutines.

Tuy nhiên, câu trả lời của Guido rất rõ ràng: "Khi tạo một nhiệm vụ từ một chương trình điều tra, bạn nên sử dụng tên phù hợp loop.create_task()"

Khi nào coroutines nên được gói trong các nhiệm vụ?

Kết thúc quy trình đăng ký trong một Nhiệm vụ - là một cách để bắt đầu quy trình đăng ký này "trong nền". Đây là ví dụ:

import asyncio


async def msg(text):
    await asyncio.sleep(0.1)
    print(text)


async def long_operation():
    print('long_operation started')
    await asyncio.sleep(3)
    print('long_operation finished')


async def main():
    await msg('first')

    # Now you want to start long_operation, but you don't want to wait it finised:
    # long_operation should be started, but second msg should be printed immediately.
    # Create task to do so:
    task = asyncio.ensure_future(long_operation())

    await msg('second')

    # Now, when you want, you can await task finised:
    await task


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Đầu ra:

first
long_operation started
second
long_operation finished

Bạn có thể thay thế asyncio.ensure_future(long_operation())bằng chỉ await long_operation()để cảm nhận sự khác biệt.


3
Theo Guido, bạn nên sử dụng create_tasknếu bạn thực sự cần aa đối tượng nhiệm vụ, mà bạn thường không cần: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0

@ bj0 cảm ơn bạn về liên kết này. Tôi đã cập nhật câu trả lời thêm thông tin từ cuộc thảo luận này.
Mikhail Gerasimov

không ensure_futuretự động thêm tạo ra Taskvòng lặp sự kiện chính?
AlQuemist

@AlQuemist mọi quy trình đăng quang, tương lai hoặc nhiệm vụ bạn tạo sẽ tự động được liên kết với một số vòng lặp sự kiện, nơi nó sẽ được thực thi sau đó. Theo mặc định, nó là vòng lặp sự kiện hiện tại cho luồng hiện tại, nhưng bạn có thể chỉ định vòng lặp sự kiện khác bằng cách sử dụng loopđối số từ khóa ( xem chữ ký ensure_future ).
Mikhail Gerasimov

2
@laycat chúng ta cần awaitbên trong msg()để trả lại quyền điều khiển cho vòng lặp sự kiện trong cuộc gọi thứ hai. Vòng lặp sự kiện khi nhận được quyền điều khiển sẽ có thể bắt đầu long_operation(). Nó được thực hiện để chứng minh cách ensure_futurekhởi động chương trình đăng quang để thực thi đồng thời với luồng thực thi hiện tại.
Mikhail Gerasimov

45

create_task()

  • chấp nhận các quy trình,
  • trả về Nhiệm vụ,
  • nó được gọi trong ngữ cảnh của vòng lặp.

ensure_future()

  • chấp nhận Futures, coroutines, các đối tượng có thể chờ đợi,
  • trả về Nhiệm vụ (hoặc Tương lai nếu Tương lai đã qua).
  • nếu đối số đã cho là một quy trình mà nó sử dụng create_task,
  • đối tượng vòng lặp có thể được thông qua.

Như bạn có thể thấy, create_task cụ thể hơn.


async hàm mà không có create_task hoặc ensure_future

asyncHàm gọi đơn giản trả về quy trình đăng quang

>>> async def doit(i):
...     await asyncio.sleep(3)
...     return i
>>> doit(4)   
<coroutine object doit at 0x7f91e8e80ba0>

Và vì phần gatherdưới bảo đảm ( ensure_future) rằng args là tương lai, rõ ràng ensure_futurelà dư thừa.

Câu hỏi tương tự Sự khác nhau giữa loop.create_task, asyncio.async / ensure_future và Task là gì?


13

Lưu ý: Chỉ hợp lệ cho Python 3.7 (đối với Python 3.5, hãy tham khảo câu trả lời trước đó ).

Từ các tài liệu chính thức:

asyncio.create_task(được thêm vào trong Python 3.7) là cách thích hợp hơn để tạo các tác vụ mới thay vì ensure_future().


Chi tiết:

Vì vậy, bây giờ, trong Python 3.7 trở đi, có 2 hàm trình bao bọc cấp cao nhất (tương tự nhưng khác nhau):

Vâng, hoàn toàn cả hai hàm trình bao bọc này sẽ giúp bạn gọi BaseEventLoop.create_task. Sự khác biệt duy nhất là ensure_futurechấp nhận bất kỳ awaitableđối tượng nào và giúp bạn chuyển đổi nó thành Tương lai. Và bạn cũng có thể cung cấp event_loopthông số của riêng mình trong ensure_future. Và tùy thuộc vào việc bạn có cần những khả năng đó hay không, bạn có thể chỉ cần chọn trình bao bọc để sử dụng.


Tôi nghĩ rằng có một sự khác biệt khác không được ghi lại: nếu bạn cố gắng gọi asyncio.create_task trước khi chạy vòng lặp, bạn sẽ gặp sự cố vì asyncio.create_task đang mong đợi một vòng lặp đang chạy. Tuy nhiên, bạn có thể sử dụng asyncio.ensure_future trong trường hợp này, vì vòng lặp đang chạy không phải là bắt buộc.
coelhudo

4

đối với ví dụ của bạn, tất cả ba kiểu thực thi không đồng bộ. sự khác biệt duy nhất là, trong ví dụ thứ ba, bạn đã tạo trước tất cả 10 coroutines và cùng nhau gửi đến vòng lặp. vì vậy chỉ có cái cuối cùng cho đầu ra một cách ngẫu nhiên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.