2009-11-26 20 views
10

Estoy tratando de convertir una biblioteca síncrona para usar un marco IO asíncrono interno. Tengo varios métodos que se ven así:detonantes de llamada y generadores asíncronos de Python

def foo: 
    .... 
    sync_call_1() # synchronous blocking call 
    .... 
    sync_call_2() # synchronous blocking call 
    .... 
    return bar 

Para cada una de las funciones sincrónicas (sync_call_*), he escrito una función asíncrona correspondiente que tiene un una devolución de llamada. P.ej.

def async_call_1(callback=none): 
    # do the I/O 
    callback() 

Ahora la pregunta pitón novato - cuál es la forma más fácil de traducir los métodos existentes para utilizar estos nuevos métodos asincrónicos en su lugar? Es decir, el método foo() anterior tiene que ser ahora:

def async_foo(callback): 
    # Do the foo() stuff using async_call_* 
    callback() 

Una opción obvia es pasar una devolución de llamada en cada método asíncrono que efectivamente "reanuda" la función "foo" llamando, y luego llamar a la devolución de llamada mundial a el final del método Sin embargo, eso hace que el código sea frágil, feo y necesitaría agregar una nueva devolución de llamada para cada llamada a un método async_call_*.

¿Hay una manera fácil de hacerlo usando una expresión de pitón, como un generador o coroutine?

Respuesta

2

Existen varias formas de tareas de multiplexión. No podemos decir qué es lo mejor para su caso sin un conocimiento más profundo de lo que está haciendo. Probablemente la forma más fácil/universal es usar hilos. Eche un vistazo a this question para algunas ideas.

10

ACTUALIZACIÓN: tomar esto con un grano de sal, ya que estoy fuera de contacto con la evolución pitón asincrónicos modernas, como gevent y asyncio y en realidad no tienen experiencia seria con código asíncrono.


Hay 3 métodos comunes para enhebrar-menos código asíncrono en Python:

  1. devoluciones de llamada - feo, pero viable, Twisted lo hace así.

  2. Generadores - bien, pero requieren todos su código para seguir el estilo.

  3. Utilice la implementación de Python con tasklets reales: Stackless (RIP) y greenlet.

Desafortunadamente, idealmente todo el programa debería usar un estilo, o las cosas se vuelven complicadas. Si está de acuerdo con que su biblioteca exponga una interfaz totalmente síncrona, probablemente esté bien, pero si quiere que varias llamadas a su biblioteca trabajen en paralelo, especialmente en paralelo con otro código asincrónico, entonces necesita un evento común "reactor "Eso puede funcionar con todo el código.

Así que si tiene (o espera que el usuario tenga) otro código asíncrono en la aplicación, adoptar el mismo modelo probablemente sea inteligente.

Si no quiere entender todo el lío, considere el uso de hilos viejos malos. También son feos, pero trabajan con todo lo demás.

Si quieres entender cómo las coroutines pueden ayudarte, y cómo pueden complicarte, David Beazley's "A Curious Course on Coroutines and Concurrency" es algo bueno.

Greenlets podría ser la forma más limpia si puede usar la extensión. No tengo ninguna experiencia con ellos, así que no puedo decir mucho.

2

Usted necesita hacer la función foo también asincrónica. ¿Qué hay de este enfoque?

@make_async 
def foo(somearg, callback): 
    # This function is now async. Expect a callback argument. 
    ... 

    # change 
    #  x = sync_call1(somearg, some_other_arg) 
    # to the following: 
    x = yield async_call1, somearg, some_other_arg 
    ... 

    # same transformation again 
    y = yield async_call2, x 
    ... 

    # change 
    #  return bar 
    # to a callback call 
    callback(bar) 

Y make_async puede definirse así:

def make_async(f): 
    """Decorator to convert sync function to async 
    using the above mentioned transformations""" 
    def g(*a, **kw): 
     async_call(f(*a, **kw)) 
    return g 

def async_call(it, value=None): 
    # This function is the core of async transformation. 

    try: 
     # send the current value to the iterator and 
     # expect function to call and args to pass to it 
     x = it.send(value) 
    except StopIteration: 
     return 

    func = x[0] 
    args = list(x[1:]) 

    # define callback and append it to args 
    # (assuming that callback is always the last argument) 

    callback = lambda new_value: async_call(it, new_value) 
    args.append(callback) 

    func(*args) 

PRECAUCIÓN: No he probado esta