Chan
Me gustaría usar asyncio para obtener la página web html.
Ejecuto el siguiente código en jupyter notebook:
import aiofiles
import aiohttp
from aiohttp import ClientSession
async def get_info(url, session):
resp = await session.request(method="GET", url=url)
resp.raise_for_status()
html = await resp.text(encoding='GB18030')
with open('test_asyncio.html', 'w', encoding='utf-8-sig') as f:
f.write(html)
return html
async def main(urls):
async with ClientSession() as session:
tasks = [get_info(url, session) for url in urls]
return await asyncio.gather(*tasks)
if __name__ == "__main__":
url = ['http://huanyuntianxiazh.fang.com/house/1010123799/housedetail.htm', 'http://zhaoshangyonghefu010.fang.com/house/1010126863/housedetail.htm']
result = asyncio.run(main(url))
Sin embargo, vuelve RuntimeError: asyncio.run() cannot be called from a running event loop
¿Cuál es el problema?
¿Cómo resolverlo?
cglacet
los asyncio.run()
documentación dice:
Esta función no poder se llamará cuando se esté ejecutando otro bucle de eventos asyncio en el mismo subproceso.
En tu caso, jupyter (Python ≥ 7.0) ya está ejecutando un bucle de eventos:
Ahora puede usar async/await en el nivel superior en la terminal de IPython y en la computadora portátil; en la mayoría de los casos, debería, en la mayoría de los casos, “simplemente funcionar”. Actualice IPython a la versión 7+, IPykernel a la versión 5+, y estará listo para la carrera.
Por lo tanto, no necesita iniciar el ciclo de eventos usted mismo y, en su lugar, puede llamar await main(url)
directamente, incluso si su código se encuentra fuera de cualquier función asíncrona.
Jupyter/IPython
async def main():
print(1)
await main()
Python (≥ 3.7) o versiones anteriores de IPython
import asyncio
async def main():
print(1)
asyncio.run(main())
En tu código eso daría:
url = ['url1', 'url2']
result = await main(url)
for text in result:
pass # text contains your html (text) response
Precaución
Hay un Pequeña diferencia sobre cómo Jupyter usa el bucle en comparación con IPython.
-
Gracias, cglacet. Sin embargo, hay una advertencia:
c:\program files\python37\lib\site-packages\ipykernel_launcher.py:29: RuntimeWarning: coroutine 'main' was never awaited
– Chan
29 de marzo de 2019 a las 2:32
-
Eso es probablemente porque llamaste
main(url)
en vez deawait main(url)
.– cglacet
29 de marzo de 2019 a las 2:33
-
¿Hay alguna manera de tener un fragmento de código que funcione tanto dentro como fuera de Jupyter?
– Foad S. Farimani
2 de abril de 2020 a las 10:58
-
No sé si es posible, pero no estoy seguro de que realmente quieras eso de todos modos. Tal vez haga esto como una pregunta separada y vea si las personas tienen ideas sobre si es posible o deseable.
– cglacet
11 de abril de 2020 a las 13:06
-
Curiosamente, cuando ejecuto lo anterior en jupyter, obtengo: SyntaxError: ‘await’ outside function
– Lucas
27 de julio de 2020 a las 22:55
Juan Monet
Para añadir a cglacet
La respuesta de – si uno quiere detectar si un bucle se está ejecutando y ajustar automáticamente (es decir, ejecutar main()
en el bucle existente, de lo contrario asyncio.run()
), aquí hay un fragmento que puede resultar útil:
# async def main():
# ...
try:
loop = asyncio.get_running_loop()
except RuntimeError: # 'RuntimeError: There is no current event loop...'
loop = None
if loop and loop.is_running():
print('Async event loop already running. Adding coroutine to the event loop.')
tsk = loop.create_task(main())
# ^-- https://docs.python.org/3/library/asyncio-task.html#task-object
# Optionally, a callback function can be executed when the coroutine completes
tsk.add_done_callback(
lambda t: print(f'Task done with result={t.result()} << return val of main()'))
else:
print('Starting new event loop')
result = asyncio.run(main())
-
Bien, de hecho, agregar una devolución de llamada aquí es probablemente la única forma de recuperar el resultado. Probablemente sería interesante tener este código en una función, por lo que solo tendría que llamar a algo como
run(main(*args, **kwargs))
. La función también podría asegurarse de recuperar su salida, aunque no estoy seguro de que sea realmente simple (debido a la devolución de llamada).– cglacet
29 abr 2020 a las 22:35
-
Esta es una solución intrigante. ¿Cómo obtendrías
t.result()
en una variable en lugar de imprimirla?– alec_djinn
10 de agosto a las 12:13
-
@alec_djinn no hay una respuesta fácil inmediata (al menos no de dentro del código de sincronización), desde la introducción de un método de bloqueo en el hilo actual, es decir. en código de sincronización (como
Queue.get()
con unQueue.put(t.result())
en la devolución de llamada) también bloquearía la ejecución del bucle asíncrono existente (en el hilo actual), por lo que no llegaría ningún resultado -> interbloqueo. Si está en un Jupyter NB, aún puede usar un sistema de mensajería y verificar manualmente el resultado (sin bloqueo). Nota usandotime.sleep()
en el código de sincronización (subproceso actual) no ayuda, ya que también pausaría el bucle asíncrono existente.–Jean Monet
10 de agosto a las 13:08
-
los documentos para
nest_asyncio
mencionar un informe de problemas con asyncio donde se indica explícitamente que este no es un comportamiento previsto para asyncio. Entonces consideraríanest_asyncio
un gran truco en el que no confiaría en mi base de código para no romper todo con una nueva versión de Python.– Gregor Mullegger
14 oct 2020 a las 11:29
-
Otro problema es que nest_asyncio requiere Python 3.5 o superior, lo que no me resulta útil porque estoy atascado en Python 2.7.
– mknote
17 de noviembre de 2020 a las 21:23
-
esto me permitió resolver el problema del código importado (donde no puedes cambiarlo tú mismo)
– Álex S.
30 de noviembre de 2020 a las 17:16
-
Esto funcionó muy bien solucionando un problema de asyncio importado, muchas gracias
– Mika C.
9 de mayo a las 19:15
Marca
Combinando los métodos de Pankaj Sharma y Jean Monet, escribí el siguiente fragmento que actúa como asyncio.run (con una sintaxis ligeramente diferente), pero también funciona dentro de un cuaderno Jupyter.
class RunThread(threading.Thread):
def __init__(self, func, args, kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
self.result = None
super().__init__()
def run(self):
self.result = asyncio.run(self.func(*self.args, **self.kwargs))
def run_async(func, *args, **kwargs):
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
thread = RunThread(func, args, kwargs)
thread.start()
thread.join()
return thread.result
else:
return asyncio.run(func(*args, **kwargs))
Uso:
async def test(name):
await asyncio.sleep(5)
return f"hello {name}"
run_async(test, "user") # blocks for 5 seconds and returns "hello user"
Encontré el unsync
paquete útil para escribir código que se comporte de la misma manera en una secuencia de comandos de Python y en Jupyter REPL.
import asyncio
from unsync import unsync
@unsync
async def demo_async_fn():
await asyncio.sleep(0.1)
return "done!"
print(demo_async_fn().result())
Pankaj Sharma
Como cglacet mencionó que la documentación dice
No se puede llamar a esta función cuando se está ejecutando otro bucle de eventos asyncio en el mismo subproceso.
Puedes usar otro hilo, es decir:
class ResolveThread(threading.Thread):
def __init__(self,result1,fun,url):
self.result1= result1
self.fun = fun
self.url = url
threading.Thread.__init__(self)
def run(self):
result1[0] = asyncio.run(self.fun(self.url))
result1 = [None]
sp = ResolveThread(result1)
sp.start()
sp.join() # connect main thread
result = result1[0]