"Cháy và quên" python async / await


115

Đôi khi có một số hoạt động không đồng bộ không quan trọng cần xảy ra nhưng tôi không muốn đợi nó hoàn thành. Trong quá trình triển khai đăng ký của Tornado, bạn có thể "kích hoạt và quên" một hàm không đồng bộ bằng cách đơn giản bỏ qua từ yieldkhóa.

Tôi đã cố gắng tìm ra cách "khai hỏa và quên" với cú pháp async/ mới awaitđược phát hành trong Python 3.5. Ví dụ: một đoạn mã đơn giản:

async def async_foo():
    print("Do some stuff asynchronously here...")

def bar():
    async_foo()  # fire and forget "async_foo()"

bar()

Tuy nhiên, điều xảy ra là nó bar()không bao giờ thực thi và thay vào đó chúng tôi nhận được cảnh báo thời gian chạy:

RuntimeWarning: coroutine 'async_foo' was never awaited
  async_foo()  # fire and forget "async_foo()"

Có liên quan? stackoverflow.com/q/32808893/1639625 Trên thực tế, tôi nghĩ rằng nó là một bản sao, nhưng tôi không muốn làm nhanh nó. Ai đó có thể xác nhận?
tobias_k

3
@tobias_k, tôi không nghĩ nó trùng lặp. Câu trả lời tại liên kết quá rộng để có thể trả lời cho câu hỏi này.
Mikhail Gerasimov

2
(1) tiến trình "chính" của bạn có tiếp tục chạy mãi mãi không? Hoặc (2) bạn có muốn cho phép quy trình của bạn chết nhưng cho phép các nhiệm vụ bị quên vẫn tiếp tục công việc của chúng? Hay (3) bạn thích quá trình chính của mình chờ đợi các nhiệm vụ bị quên ngay trước khi kết thúc?
Julien Palard

Câu trả lời:


170

Cập nhật:

Thay thế asyncio.ensure_futurebằng asyncio.create_taskmọi nơi nếu bạn đang sử dụng Python> = 3.7 Đây là cách mới hơn, đẹp hơn để tạo tác vụ .


asyncio.Task để "chữa cháy và quên đi"

Theo tài liệu python asyncio.Task, có thể bắt đầu một số quy trình đăng ký để thực thi "trong nền" . Tác vụ được tạo bởi asyncio.ensure_future hàm sẽ không chặn việc thực thi (do đó hàm sẽ trả về ngay lập tức!). Đây có vẻ như là một cách để "bắn và quên" như bạn đã yêu cầu.

import asyncio


async def async_foo():
    print("async_foo started")
    await asyncio.sleep(1)
    print("async_foo done")


async def main():
    asyncio.ensure_future(async_foo())  # fire and forget async_foo()

    # btw, you can also create tasks inside non-async funcs

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


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

Đầu ra:

Do some actions 1
async_foo started
Do some actions 2
async_foo done
Do some actions 3

Điều gì sẽ xảy ra nếu các tác vụ đang thực thi sau khi vòng lặp sự kiện hoàn tất?

Lưu ý rằng asyncio dự kiến ​​nhiệm vụ sẽ được hoàn thành tại thời điểm hoàn thành vòng lặp sự kiện. Vì vậy, nếu bạn sẽ thay đổi main()thành:

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')

Bạn sẽ nhận được cảnh báo này sau khi chương trình kết thúc:

Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]

Để tránh điều đó, bạn chỉ có thể đợi tất cả các nhiệm vụ đang chờ xử lý sau khi hoàn thành vòng lặp sự kiện:

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')


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

    # Let's also finish all running tasks:
    pending = asyncio.Task.all_tasks()
    loop.run_until_complete(asyncio.gather(*pending))

Giết nhiệm vụ thay vì chờ đợi chúng

Đôi khi bạn không muốn đợi các tác vụ được thực hiện (ví dụ: một số tác vụ có thể được tạo để chạy mãi mãi). Trong trường hợp đó, bạn chỉ có thể hủy () chúng thay vì chờ chúng:

import asyncio
from contextlib import suppress


async def echo_forever():
    while True:
        print("echo")
        await asyncio.sleep(1)


async def main():
    asyncio.ensure_future(echo_forever())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


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

    # Let's also cancel all running tasks:
    pending = asyncio.Task.all_tasks()
    for task in pending:
        task.cancel()
        # Now we should await task to execute it's cancellation.
        # Cancelled task raises asyncio.CancelledError that we can suppress:
        with suppress(asyncio.CancelledError):
            loop.run_until_complete(task)

Đầu ra:

Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo

Tôi đã sao chép và vượt qua khối đầu tiên và chỉ đơn giản là chạy nó trên đầu của tôi và vì lý do nào đó tôi nhận được: dòng 4 async def async_foo (): ^ Như thể có một số lỗi cú pháp với định nghĩa hàm trên dòng 4: "async def async_foo ( ):" Tui bỏ lỡ điều gì vậy?
Gil Allen

3
@GilAllen cú pháp này chỉ hoạt động trong Python 3.5+. Python 3.4 cần cú pháp cũ (xem docs.python.org/3.4/library/asyncio-task.html ). Python 3.3 trở xuống hoàn toàn không hỗ trợ asyncio.
Mikhail Gerasimov

Bạn sẽ hủy các nhiệm vụ trong một luồng như thế nào?… ̣Tôi có một luồng tạo ra một số nhiệm vụ và tôi muốn giết tất cả các tác vụ đang chờ xử lý khi luồng chết trong stop()phương thức của nó .
Sardathrion - chống lại sự lạm dụng SE.

@Sardathrion Tôi không chắc liệu nhiệm vụ có điểm ở đâu đó trên chuỗi mà nó được tạo hay không, nhưng không có gì ngăn bạn theo dõi chúng theo cách thủ công: ví dụ: chỉ cần thêm tất cả các nhiệm vụ được tạo trong chuỗi vào danh sách và khi đến thời điểm hãy hủy chúng theo cách được giải thích ở trên.
Mikhail Gerasimov

2
Lưu ý rằng "Task.all_tasks () không được dùng nữa kể từ Python 3.7, hãy sử dụng asyncio.all_tasks () để thay thế"
Alexis

12

Cảm ơn Sergey vì câu trả lời thành công. Đây là phiên bản được trang trí của giống nhau.

import asyncio
import time

def fire_and_forget(f):
    def wrapped(*args, **kwargs):
        return asyncio.get_event_loop().run_in_executor(None, f, *args, *kwargs)

    return wrapped

@fire_and_forget
def foo():
    time.sleep(1)
    print("foo() completed")

print("Hello")
foo()
print("I didn't wait for foo()")

Sản xuất

>>> Hello
>>> foo() started
>>> I didn't wait for foo()
>>> foo() completed

Lưu ý: Kiểm tra câu trả lời khác của tôi, câu trả lời tương tự bằng cách sử dụng các chuỗi đơn giản.


Tôi đã gặp phải tình trạng chậm lại đáng kể sau khi sử dụng phương pháp này, tạo ra ~ 5 tác vụ nhỏ và quên mỗi giây. Không sử dụng nó trong sản xuất cho một nhiệm vụ dài hạn. Nó sẽ ăn CPU và bộ nhớ của bạn!
pir

10

Đây không phải là thực thi hoàn toàn không đồng bộ, nhưng có thể run_in_executor () phù hợp với bạn.

def fire_and_forget(task, *args, **kwargs):
    loop = asyncio.get_event_loop()
    if callable(task):
        return loop.run_in_executor(None, task, *args, **kwargs)
    else:    
        raise TypeError('Task must be a callable')

def foo():
    #asynchronous stuff here


fire_and_forget(foo)

3
Câu trả lời ngắn gọn tốt đẹp. Điều đáng chú ý là executormặc định sẽ gọi concurrent.futures.ThreadPoolExecutor.submit(). Tôi đề cập đến bởi vì việc tạo chủ đề không miễn phí; fire-và-quên 1000 lần trong một giây có thể sẽ đưa một chủng lớn về quản lý chủ đề
Brad Solomon

Vâng. Tôi đã không để ý đến cảnh báo của bạn và đã gặp phải tình trạng chậm lại đáng kể sau khi sử dụng phương pháp này, tạo ra ~ 5 nhiệm vụ nhỏ và quên mỗi giây. Không sử dụng nó trong sản xuất cho một nhiệm vụ dài hạn. Nó sẽ ăn CPU và bộ nhớ của bạn!
pir

3

Vì một số lý do nếu bạn không thể sử dụng asynciothì đây là cách triển khai sử dụng các chuỗi đơn giản. Kiểm tra các câu trả lời khác của tôi và cả câu trả lời của Sergey.

import threading

def fire_and_forget(f):
    def wrapped():
        threading.Thread(target=f).start()

    return wrapped

@fire_and_forget
def foo():
    time.sleep(1)
    print("foo() completed")

print("Hello")
foo()
print("I didn't wait for foo()")

Nếu chúng ta chỉ cần chức năng fire_and_forget này và không cần gì khác từ asyncio, thì liệu sử dụng asyncio có tốt hơn không? Những lợi ích là gì?
pir
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.