programing

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

subpage 2023. 5. 14. 10:39
반응형

주피터 노트북을 사용할 때 "실행 중인 이벤트 루프에서 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))

그러나, 그것은 돌아옵니다.RuntimeError: asyncio.run() cannot be called from a running event loop

뭐가 문제야?

어떻게 풀까요?

설명서에는 다음과 같이 나와 있습니다.

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

이 경우 주피터(IPython 7.0)는 이미 이벤트 루프를 실행하고 있습니다.

이제 IPython 터미널에서 최상위 수준의 비동기/대기를 사용할 수 있으며, 노트북에서는 대부분의 경우 "그냥 작동"해야 합니다.IPython을 버전 7+로 업데이트하고 커널을 버전 5+로 업데이트하면 레이스를 시작할 수 있습니다.

따라서 이벤트 루프를 직접 시작할 필요가 없으며 대신 전화를 걸 수 있습니다.await main(url)직접적으로, 코드가 비동기 함수의 외부에 있더라도.

목성 (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

주의.

주피터가 루프를 사용하는 방식은 IPython과 비교했을 때 약간의 차이가 있습니다.

추가하기cglacet의 답변 - 루프가 실행 중인지 여부를 감지하고 자동으로 조정(즉, 실행)하려는 경우main()기존 루프에서, 그렇지 않으면asyncio.run()), 다음은 유용할 수 있는 코드 조각입니다.

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

다음을 사용합니다.

https://github.com/erdewit/nest_asyncio

import nest_asyncio
nest_asyncio.apply()

Pankaj Sharma와 Jean Monet의 방법을 결합하여 다음 스니펫을 작성했습니다. asyncio.run 역할을 하지만 주피터 노트북에서도 작동합니다.

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"

저는 패키지가 파이썬 스크립트와 주피터 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())

기존 답변에 추가하자면, 외부 라이브러리가 없는 다소 짧은 버전으로 주피터 내부와 외부에서 실행되고 반환 값을 가져올 수 있습니다.

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

Cglace가 언급한 바와 같이 설명서가 나와 있습니다.

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

다른 스레드를 사용할 수 있습니다. 예:

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]

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)

사용하다run_async()맘에 들다async.run(),예.,run_async(test("user")).

언급URL : https://stackoverflow.com/questions/55409641/asyncio-run-cannot-be-called-from-a-running-event-loop-when-using-jupyter-no

반응형