Chiamata di metodo asincrona in Python?


178

Mi chiedevo se ci fosse qualche libreria per chiamate di metodo asincrone in Python . Sarebbe bello se potessi fare qualcosa del genere

@async
def longComputation():
    <code>


token = longComputation()
token.registerCallback(callback_function)
# alternative, polling
while not token.finished():
    doSomethingElse()
    if token.finished():
        result = token.result()

O per chiamare una routine non asincrona in modo asincrono

def longComputation()
    <code>

token = asynccall(longComputation())

Sarebbe bello avere una strategia più raffinata come nativa nel nucleo linguistico. È stato preso in considerazione?


A partire da Python 3.4: docs.python.org/3/library/asyncio.html (c'è un backport per 3.3 e nuovi brillanti asynce awaitsintassi da 3.5).
jonrsharpe,

Non esiste un meccanismo di callback, ma è possibile aggregare i risultati in un dizionario basato sul modulo multiprocessing di Python. Sono sicuro che puoi aggiungere un altro parametro alla funzione decorata come callback. github.com/alex-sherman/deco .
RajaRaviVarma,

Per iniziare. Documentazione ufficiale - docs.python.org/3/library/concurrency.html
Adarsh ​​Madrecha

Risposte:


142

Puoi usare il modulo multiprocessore aggiunto in Python 2.6. È possibile utilizzare pool di processi e quindi ottenere risultati in modo asincrono con:

apply_async(func[, args[, kwds[, callback]]])

Per esempio:

from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    pool = Pool(processes=1)              # Start a worker processes.
    result = pool.apply_async(f, [10], callback) # Evaluate "f(10)" asynchronously calling callback when finished.

Questa è solo un'alternativa. Questo modulo offre molti servizi per raggiungere ciò che desideri. Inoltre sarà davvero facile fare un decoratore da questo.


5
Lucas S., il tuo esempio non funziona, sfortunatamente. La funzione di callback non viene mai chiamata.
Data accordo

6
Vale probabilmente la pena ricordare che ciò genera processi separati anziché thread separati all'interno di un processo. Questo potrebbe avere alcune implicazioni.
user47741

11
Funziona: result = pool.apply_async (f, [10], callback = finish)
MJ

6
Per fare veramente qualsiasi cosa in modo asincrono in Python è necessario utilizzare il modulo multiprocessore per generare nuovi processi. Creare semplicemente nuovi thread è ancora in balia del Global Interpreter Lock che impedisce a un processo Python di fare più cose contemporaneamente.
Drahkar,

2
Nel caso in cui non si desideri generare un nuovo processo durante l'utilizzo di questa soluzione, modificare l'importazione in from multiprocessing.dummy import Pool. multiprocessing.dummy ha lo stesso identico comportamento implementato sui thread anziché sui processi
Almog Cohen,

203

Qualcosa di simile a:

import threading

thr = threading.Thread(target=foo, args=(), kwargs={})
thr.start() # Will run "foo"
....
thr.is_alive() # Will return whether foo is running currently
....
thr.join() # Will wait till "foo" is done

Consulta la documentazione su https://docs.python.org/library/threading.html per maggiori dettagli.


1
sì, se hai solo bisogno di fare le cose in modo asincrono, perché non usare solo thread? dopo che tutto il filo è leggero del processo
kk1957

22
Nota importante: l'implementazione standard (CPython) dei thread non aiuterà con le attività legate al calcolo, a causa del "Blocco dell'interprete globale". Vedi il documento della biblioteca: link
solublefish,

3
L'uso di thread.join () è davvero asincrono? Cosa succede se non si desidera bloccare un thread (ad esempio un thread dell'interfaccia utente) e non utilizzare una tonnellata di risorse facendo un ciclo while su di esso?
Mgamerz,

1
L'unione @Mgamerz è sincrona. È possibile consentire al thread di inserire i risultati dell'esecuzione in una coda o / e chiamare un callback. Altrimenti non sai quando è finito (se non del tutto).
Drakosha,

1
È possibile chiamare una funzione di callback alla fine dell'esecuzione del thread come si può fare con il multiprocessing.Pool
Reda Drissi

49

A partire da Python 3.5, è possibile utilizzare generatori avanzati per le funzioni asincrone.

import asyncio
import datetime

Sintassi del generatore avanzata:

@asyncio.coroutine
def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        yield from asyncio.sleep(1)


loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(display_date(loop))
loop.close()

Nuova async/awaitsintassi:

async def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)


loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(display_date(loop))
loop.close()

8
@carnabeh, potresti estendere questo esempio per includere la funzione "def longComputation ()" dell'OP? La maggior parte degli esempi usa "await asyncio.sleep (1)", ma se longComputation () restituisce, diciamo, un doppio, non puoi semplicemente usare "await longComputation ()".
Fab,

Dieci anni in futuro e questa dovrebbe essere la risposta accettata ora. Quando parli di asincrono in python3.5 + ciò che ti viene in mente dovrebbe essere asyncio e parola chiave asincrona.
zeh


21

Puoi implementare un decoratore per rendere le tue funzioni asincrone, anche se è un po 'complicato. Il multiprocessingmodulo è pieno di piccole stranezze e restrizioni apparentemente arbitrarie - una ragione in più per incapsularlo dietro un'interfaccia amichevole.

from inspect import getmodule
from multiprocessing import Pool


def async(decorated):
    r'''Wraps a top-level function around an asynchronous dispatcher.

        when the decorated function is called, a task is submitted to a
        process pool, and a future object is returned, providing access to an
        eventual return value.

        The future object has a blocking get() method to access the task
        result: it will return immediately if the job is already done, or block
        until it completes.

        This decorator won't work on methods, due to limitations in Python's
        pickling machinery (in principle methods could be made pickleable, but
        good luck on that).
    '''
    # Keeps the original function visible from the module global namespace,
    # under a name consistent to its __name__ attribute. This is necessary for
    # the multiprocessing pickling machinery to work properly.
    module = getmodule(decorated)
    decorated.__name__ += '_original'
    setattr(module, decorated.__name__, decorated)

    def send(*args, **opts):
        return async.pool.apply_async(decorated, args, opts)

    return send

Il codice seguente illustra l'utilizzo del decoratore:

@async
def printsum(uid, values):
    summed = 0
    for value in values:
        summed += value

    print("Worker %i: sum value is %i" % (uid, summed))

    return (uid, summed)


if __name__ == '__main__':
    from random import sample

    # The process pool must be created inside __main__.
    async.pool = Pool(4)

    p = range(0, 1000)
    results = []
    for i in range(4):
        result = printsum(i, sample(p, 100))
        results.append(result)

    for result in results:
        print("Worker %i: sum value is %i" % result.get())

In un caso del mondo reale mi occuperei un po 'di più del decoratore, fornendo un modo per disattivarlo per il debug (mantenendo al suo posto l'interfaccia futura), o forse una struttura per gestire le eccezioni; ma penso che questo dimostri abbastanza bene il principio.


Questa dovrebbe essere la risposta migliore. Adoro come possa restituire valore. Non come il thread che si esegue semplicemente in modo asincrono.
Aminah Nuraini,

16

Appena

import threading, time

def f():
    print "f started"
    time.sleep(3)
    print "f finished"

threading.Thread(target=f).start()

8

È possibile utilizzare eventlet. Ti consente di scrivere quello che sembra essere un codice sincrono, ma di farlo funzionare in modo asincrono sulla rete.

Ecco un esempio di un crawler super minimale:

urls = ["http://www.google.com/intl/en_ALL/images/logo.gif",
     "https://wiki.secondlife.com/w/images/secondlife.jpg",
     "http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif"]

import eventlet
from eventlet.green import urllib2

def fetch(url):

  return urllib2.urlopen(url).read()

pool = eventlet.GreenPool()

for body in pool.imap(fetch, urls):
  print "got body", len(body)

7

La mia soluzione è:

import threading

class TimeoutError(RuntimeError):
    pass

class AsyncCall(object):
    def __init__(self, fnc, callback = None):
        self.Callable = fnc
        self.Callback = callback

    def __call__(self, *args, **kwargs):
        self.Thread = threading.Thread(target = self.run, name = self.Callable.__name__, args = args, kwargs = kwargs)
        self.Thread.start()
        return self

    def wait(self, timeout = None):
        self.Thread.join(timeout)
        if self.Thread.isAlive():
            raise TimeoutError()
        else:
            return self.Result

    def run(self, *args, **kwargs):
        self.Result = self.Callable(*args, **kwargs)
        if self.Callback:
            self.Callback(self.Result)

class AsyncMethod(object):
    def __init__(self, fnc, callback=None):
        self.Callable = fnc
        self.Callback = callback

    def __call__(self, *args, **kwargs):
        return AsyncCall(self.Callable, self.Callback)(*args, **kwargs)

def Async(fnc = None, callback = None):
    if fnc == None:
        def AddAsyncCallback(fnc):
            return AsyncMethod(fnc, callback)
        return AddAsyncCallback
    else:
        return AsyncMethod(fnc, callback)

E funziona esattamente come richiesto:

@Async
def fnc():
    pass

5

Qualcosa del genere funziona per me, puoi quindi chiamare la funzione e si spedirà su un nuovo thread.

from thread import start_new_thread

def dowork(asynchronous=True):
    if asynchronous:
        args = (False)
        start_new_thread(dowork,args) #Call itself on a new thread.
    else:
        while True:
            #do something...
            time.sleep(60) #sleep for a minute
    return

2

C'è qualche motivo per non usare i thread? Puoi usare la threadingclasse. Invece di finished()utilizzare la funzione isAlive(). La result()funzione potrebbe join()eseguire il thread e recuperare il risultato. E, se possibile, sovrascrivere le funzioni run()e __init__per chiamare la funzione specificata nel costruttore e salvare il valore da qualche parte nell'istanza della classe.


2
Se si tratta di una funzione computazionalmente costosa, il threading non ti darà nulla (probabilmente renderà le cose più lente in realtà) poiché un processo Python è limitato a un core della CPU a causa del GIL.
Kurt,

2
@Kurt, sebbene sia vero, l'OP non ha menzionato che la performance era la sua preoccupazione. Ci sono altri motivi per volere un comportamento asincrono ...
Peter Hansen,

I thread in Python non sono grandi quando vuoi avere la possibilità di uccidere la chiamata del metodo asincrono, poiché solo il thread principale in Python riceve segnali.
CivFan,

2

Puoi usare concurrent.futures (aggiunto in Python 3.2).

import time
from concurrent.futures import ThreadPoolExecutor


def long_computation(duration):
    for x in range(0, duration):
        print(x)
        time.sleep(1)
    return duration * 2


print('Use polling')
with ThreadPoolExecutor(max_workers=1) as executor:
    future = executor.submit(long_computation, 5)
    while not future.done():
        print('waiting...')
        time.sleep(0.5)

    print(future.result())

print('Use callback')
executor = ThreadPoolExecutor(max_workers=1)
future = executor.submit(long_computation, 5)
future.add_done_callback(lambda f: print(f.result()))

print('waiting for callback')

executor.shutdown(False)  # non-blocking

print('shutdown invoked')

Questa è un'ottima risposta, in quanto è l'unica qui che dà la possibilità di un threadpool con callbacks
Reda Drissi

Sfortunatamente, questo soffre anche del "Global Interpreter Lock". Vedi il documento della biblioteca: link . Testato con Python 3.7
Alex

0

Puoi usare il processo. Se vuoi eseguirlo per sempre, usa mentre (come la rete) nella tua funzione:

from multiprocessing import Process
def foo():
    while 1:
        # Do something

p = Process(target = foo)
p.start()

se vuoi solo eseguirlo una volta, fai così:

from multiprocessing import Process
def foo():
    # Do something

p = Process(target = foo)
p.start()
p.join()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.