web-dev-qa-db-fra.com

Appel de méthode asynchrone en Python?

Je me demandais s'il existait une bibliothèque pour les appels de méthodes asynchrones dans Python . Ce serait génial si vous pouviez faire quelque chose comme

@async
def longComputation():
    <code>


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

Ou pour appeler une routine non asynchrone de manière asynchrone

def longComputation()
    <code>

token = asynccall(longComputation())

Il serait bon d’avoir une stratégie plus raffinée en tant que langue maternelle. Était-ce considéré?

153
Stefano Borini

Vous pouvez utiliser le module multitraitement ajouté dans Python 2.6. Vous pouvez utiliser des pools de processus, puis obtenir des résultats de manière asynchrone avec: 

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

Par exemple.:

from multiprocessing import Pool

def f(x):
    return x*x

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

Ce n'est qu'une alternative. Ce module fournit de nombreuses installations pour réaliser ce que vous voulez. En outre, il sera très facile de faire un décorateur à partir de cela.

129
Lucas S.

Quelque chose comme:

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

Voir la documentation sur https://docs.python.org/2/library/threading.html#module-threading pour plus d'informations Ce code devrait également fonctionner pour Python 3.

188
Drakosha

A partir de Python 3.5, vous pouvez utiliser des générateurs améliorés pour les fonctions asynchrones.

import asyncio
import datetime

Syntaxe améliorée du générateur:

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

Nouvelle syntaxe async/await:

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

Ce n’est pas dans le noyau linguistique, mais une bibliothèque très mature qui fait ce que vous voulez est Twisted . Il introduit l'objet différé, auquel vous pouvez attacher des rappels ou des gestionnaires d'erreur ("errbacks"). Un différé est fondamentalement une "promesse" qu'une fonction aura finalement un résultat.

28

Vous pouvez implémenter un décorateur pour rendre vos fonctions asynchrones, bien que ce soit un peu délicat. Le module multiprocessing est plein de petites bizarreries et de restrictions apparemment arbitraires - une raison de plus pour l'encapsuler derrière une interface conviviale.

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 __attribute. This is necessary for
    # the multiprocessing pickling machinery to work properly.
    module = getmodule(decorated)
    decorated.__+= '_original'
    setattr(module, decorated.__name__, decorated)

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

    return send

Le code ci-dessous illustre l'utilisation du décorateur:

@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 __== '__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())

Dans un cas réel, je m'attarderais un peu plus sur le décorateur, en lui fournissant un moyen de le désactiver pour le débogage (tout en maintenant l'interface future), ou peut-être un moyen de gérer les exceptions; mais je pense que cela démontre assez bien le principe.

19
xperroni

Juste

import threading, time

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

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

Ma solution est:

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)

Et fonctionne exactement comme demandé:

@Async
def fnc():
    pass
7
Nevyn

Vous pouvez utiliser Eventlet. Il vous permet d'écrire ce qui semble être du code synchrone, mais de le faire fonctionner de manière asynchrone sur le réseau.

Voici un exemple de robot super minimal:

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
Raj

Quelque chose comme cela fonctionne pour moi, vous pouvez alors appeler la fonction, et elle se dépêchera sur un nouveau fil.

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
5

Y a-t-il une raison de ne pas utiliser les threads? Vous pouvez utiliser la classe threading . Au lieu de la fonction finished(), utilisez la fonction isAlive(). La fonction result() pourrait join() le thread et récupérer le résultat. Et, si vous le pouvez, substituez les fonctions run() et __init__ pour appeler la fonction spécifiée dans le constructeur et enregistrez la valeur quelque part dans l'instance de la classe.

2
ondra

Vous pouvez utiliser concurrent.futures (ajouté dans 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')
0
Nan Zhong

Vous pouvez utiliser le processus. Si vous voulez l'exécuter pour toujours, utilisez (comme le réseau) dans votre fonction:

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

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

si vous voulez juste le lancer une fois, faites comme ça:

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

p = Process(target = foo)
p.start()
p.join()
0
Keivan