"Ateş et ve unut" python eşzamansız / bekle


115

Bazen olması gereken bazı kritik olmayan eşzamansız işlemler vardır, ancak tamamlanmasını beklemek istemiyorum. Tornado'nun coroutine uygulamasında, bir eşzamansız işlevi yalnızca yieldanahtar kelimeyi çıkararak "ateşleyin ve unutun" .

Python 3.5'te yayınlanan yeni async/ awaitsözdizimi ile nasıl "ateşle ve unut" bulmaya çalışıyorum . Örneğin, basitleştirilmiş bir kod pasajı:

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

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

bar()

Ancak olan şey, bu bar()asla çalışmaz ve bunun yerine bir çalışma zamanı uyarısı alırız:

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

İlişkili? stackoverflow.com/q/32808893/1639625 Aslında bunun bir kopya olduğunu düşünüyorum, ancak anında kopyalamak istemiyorum. Biri onaylayabilir mi?
tobias_k

3
@tobias_k, kopya olduğunu sanmıyorum. Bağlantıdaki yanıt, bu soruya yanıt verilemeyecek kadar geniş.
Mikhail Gerasimov

2
(1) "Ana" işleminiz sonsuza kadar devam ediyor mu? Veya (2) sürecinizin ölmesine izin vermek, ancak unutulan görevlerin işlerini sürdürmelerine izin vermek mi istiyorsunuz? Veya (3) ana işleminizin bitmeden hemen önce unutulmuş görevleri beklemesini mi tercih edersiniz?
Julien Palard

Yanıtlar:


171

gnc:

Python kullanıyorsanız asyncio.ensure_future, asyncio.create_taskher yerde değiştirin > = 3.7 Görev ortaya çıkarmanın daha yeni, daha hoş bir yolu .


asyncio. "Ateş et ve unut" görevi

Python belgelerine göre, "arka planda" yürütmekasyncio.Task için bazı coroutine başlatmak mümkündür . İşlev tarafından oluşturulan görev yürütmeyi engellemez (bu nedenle işlev hemen geri dönecektir!). Bu, istediğiniz gibi "ateş edip unutmanın" bir yolu gibi görünüyor.asyncio.ensure_future

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())

Çıktı:

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

Olay döngüsü tamamlandıktan sonra görevler yürütülüyorsa ne olur?

Asyncio'nun görevin olay döngüsü tamamlandığında tamamlanmasını beklediğini unutmayın. Yani şu şekilde değiştirirseniz main():

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')

Program bittikten sonra bu uyarıyı alacaksınız:

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

Bunu önlemek için, olay döngüsü tamamlandıktan sonra bekleyen tüm görevleri bekleyebilirsiniz :

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))

Görevleri beklemek yerine öldürün

Bazen görevlerin yapılmasını beklemek istemezsiniz (örneğin, bazı görevler sonsuza kadar çalışacak şekilde oluşturulabilir). Bu durumda, onları beklemek yerine iptal edebilirsiniz ():

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)

Çıktı:

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

İlk bloğu kopyalayıp geçtim ve kendi tarafımda çalıştırdım ve bir nedenden dolayı şunu elde ettim: 4. satır async def async_foo (): ^ 4. satırdaki işlev tanımında bazı sözdizimi hatası varmış gibi: "async def async_foo ( ): "Bir şey mi kaçırıyorum?
Gil Allen

3
@GilAllen bu sözdizimi yalnızca Python 3.5+ ile çalışır. Python 3.4 eski sözdizimine ihtiyaç duyar (bkz. Docs.python.org/3.4/library/asyncio-task.html ). Python 3.3 ve altı, asyncio'yu hiç desteklemez.
Mikhail Gerasimov

Bir iş parçacığındaki görevleri nasıl sonlandırırsınız?… ̣Bazı görevler oluşturan bir iş parçacığım var ve iş parçacığı kendi stop()yönteminde öldüğünde bekleyenlerin tümünü öldürmek istiyorum .
Sardathrion - SE istismarına karşı

@Sardathrion Görevin oluşturulduğu iş parçacığı üzerinde bir yere işaret edip etmediğinden emin değilim, ancak hiçbir şey onları manuel olarak izlemenizi engellemiyor: örneğin, sadece iş parçacığında oluşturulan tüm görevleri bir listeye ekleyin ve zaman geldiğinde iptal edin. yukarıda.
Mikhail Gerasimov

2
"Task.all_tasks (), Python 3.7'den beri kullanımdan kaldırıldı, bunun yerine asyncio.all_tasks () kullanın"
Alexis

12

Kısa cevap için teşekkür ederim Sergey. İşte aynısının süslü versiyonu.

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()")

üretir

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

Not: Düz iplikler kullanarak aynı şeyi yapan diğer cevabımı kontrol edin.


Saniyede ~ 5 küçük çalıştır ve unut görevi oluşturan bu yaklaşımı kullandıktan sonra önemli bir yavaşlama yaşadım. Bunu üretimde uzun süreli bir görev için kullanmayın. İşlemcinizi ve belleğinizi yer!
pir

10

Bu tamamen eşzamansız yürütme değildir, ancak belki run_in_executor () sizin için uygundur.

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
Güzel kısa cevap. executorVarsayılan olarak arama yapacağını belirtmekte fayda var concurrent.futures.ThreadPoolExecutor.submit(). Bahsediyorum çünkü konu oluşturmak bedava değil; saniyede 1000 kez ateş ve unutma, muhtemelen iplik yönetimi üzerinde büyük bir yük oluşturacaktır
Brad Solomon

Evet. Bu yaklaşımı kullandıktan sonra saniyede ~ 5 küçük ateş et ve unut görevi oluşturarak uyarınızı dikkate almadım ve önemli ölçüde yavaşlama yaşadım. Bunu üretimde uzun süreli bir görev için kullanmayın. İşlemcinizi ve belleğinizi yer!
pir

3

Bazı nedenlerden dolayı, kullanamıyorsanız, asyncioişte o zaman düz iş parçacıkları kullanan uygulama. Diğer cevaplarımı ve Sergey'in cevabını da kontrol edin.

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()")

Sadece bu fire_and_forget işlevine ve asyncio'dan başka bir şeye ihtiyacımız olmasa, asyncio kullanmak yine de daha iyi olur mu? Faydaları nedir?
pir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.