개발하자

주피터 노트북을 사용할 때 "asyncio.run()은 실행 중인 이벤트 루프에서 호출할 수 없습니다."

Cuire 2023. 9. 26. 19:42
반응형

주피터 노트북을 사용할 때 "asyncio.run()은 실행 중인 이벤트 루프에서 호출할 수 없습니다."

나는 웹페이지 html을 얻기 위해 비동기를 사용하고 싶다.

저는 주피터 노트북에서 다음 코드를 실행합니다:

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

하지만 다시 돌아온다

뭐가 문제야?

어떻게 풀까요?




문서에는 다음과 같이 나와 있습니다:

이 함수는 같은 스레드에서 다른 비동기 이벤트 루프가 실행될 때 호출됩니다.

당신의 경우 주피터()는 이미 이벤트 루프를 실행하고 있다:

이제 IPython 터미널과 노트북에서 상위 레벨에서 비동기/대기를 사용할 수 있습니다. 대부분의 경우 "그냥 작동"해야 합니다. IPython을 버전 7+로 업데이트하고, IPykernel을 버전 5+로 업데이트하면 경주에 나갈 수 있습니다.

따라서 이벤트 루프를 직접 시작할 필요가 없으며 코드가 비동기 함수 외부에 있더라도 직접 호출할 수 있습니다.

Jupyter (IPython ≥ 7.0)

async def main():
    print(1)
    
await main()

Python ≥ 3.7 및 IPython < 7.0

import asyncio

async def main():
    print(1)
    
asyncio.run(main())

당신의 코드에는 다음과 같은 것이 있습니다:

url = ['url1', 'url2']
result = await main(url)

for text in result:
    pass # text contains your html (text) response

주의

아이피톤에 비해 주피터가 루프를 어떻게 사용하는지에 대한 이유가 있다.

구글 콜랩

과거에는 구글 collab에서 더 복잡한 루프 조작을 요구했지만, 이제는 iPython ≥ 7.0(버전에서 테스트됨)에서와 같이 평이하게 작동해야 한다.




의 답변에 추가하기 위해 - 만약 누군가가 루프가 실행되고 있는지를 감지하고 자동으로 조정하기를 원한다면, 다음과 같이 유용함을 증명할 수 있는 토막글이 있다:

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



그 문서에 언급된 내용에 의하면

이 함수는 같은 스레드에서 다른 비동기 이벤트 루프가 실행 중일 때 호출할 수 없습니다.

다른 스레드를 사용할 수 있습니다. 즉 -

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]



판카즈 샤르마와 장 모네의 방법들을 결합하여, 나는 다음과 같은 syncio.run의 역할을 하지만 (조금 다른 구문을 가진) 주피터 노트 내에서도 작동하는 토막을 썼다.

import threading

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

용도:

async def test(name):
    await asyncio.sleep(5)
    return f"hello {name}"

run_async(test, "user")  # blocks for 5 seconds and returns "hello user"



그냥 이것만 사용하면 됩니다:

https://github.com/erdewit/nest_asyncio

import nest_asyncio
nest_asyncio.apply()



Python 스크립트와 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())



Mark에 의한 솔루션의 약간의 단순화:

import threading

class RunThread(threading.Thread):
    def __init__(self, coro):
        self.coro = coro
        self.result = None
        super().__init__()

    def run(self):
        self.result = asyncio.run(self.coro)


def run_async(coro):
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = None
    if loop and loop.is_running():
        thread = RunThread(coro)
        thread.start()
        thread.join()
        return thread.result
    else:
        return asyncio.run(coro)

예를 들어, 예를 들어, ...와 같이 사용합니다.




기존의 답변에 추가하기 위해, 외부 라이브러리가 없는 다소 짧은 버전으로, 목성 내부와 외부를 실행할 수 있으며 반환 값을 가져올 수 있다:

try:
    asyncio.get_running_loop()
    # we need to create a separate thread so we can block before returning
    with ThreadPoolExecutor(1) as pool:
        result = pool.submit(lambda: asyncio.run(myfunc())).result()
except RuntimeError:
    # no event loop running
    result = asyncio.run(myfunc())

반응형