[英]jupyter notebooks-safe asyncio run wrapper method for a library
我正在構建一個在內部利用 asyncio 的庫。 雖然用戶不應該意識到這一點,但內部實現目前使用asyncio.run()陶瓷包裝器包裝異步代碼。
但是,一些用戶將從 jupyter notebook 執行此庫代碼,我正在努力用對任一環境都安全的包裝器替換asyncio.run()
。
這是我嘗試過的:
ASYNC_IO_NO_RUNNING_LOOP_MSG = 'no running event loop'
def jupyter_safe_run_coroutine(async_coroutine, _test_mode: bool = False)
try:
loop = asyncio.get_running_loop()
task = loop.create_task(async_coroutine)
result = loop.run_until_complete(task) # <- fails as loop is already running
# OR
asyncio.wait_for(task, timeout=None, loop=loop) # <- fails as this is an async method
result = task.result()
except RuntimeError as e:
if _test_mode:
raise e
if ASYNC_IO_NO_RUNNING_LOOP_MSG in str(e):
return asyncio.run(async_coroutine)
except Exception as e:
raise e
我如何等待/等待async_coroutine
或loop.create_task(async_coroutine)
提供的任務/未來完成?
由於評論中所述的原因,上述方法都沒有實際進行等待。
我發現這個nest_asyncio庫是為解決這個問題而構建的:
ASYNC_IO_NO_RUNNING_LOOP_MSG = 'no running event loop'
HAS_BEEN_RUN = False
def jupyter_safe_run_coroutine(async_coroutine, _test_mode: bool = False):
global HAS_BEEN_RUN
if not HAS_BEEN_RUN:
_apply_nested_asyncio_patch()
HAS_BEEN_RUN = True
return asyncio.run(async_coroutine)
def _apply_nested_asyncio_patch():
try:
loop = asyncio.get_running_loop()
logger.info(f'as get_running_loop() returned {loop}, this environment has it`s own event loop.\n'
f'Patching with nest_asyncio')
import nest_asyncio
nest_asyncio.apply()
except RuntimeError as e:
if ASYNC_IO_NO_RUNNING_LOOP_MSG in str(e):
logger.info(f'as get_running_loop() raised {e}, this environment does not have it`s own event loop.\n'
f'No patching necessary')
else:
raise e
盡管如此,我仍然面臨一些問題:
對於上下文,庫在內部調用外部 API 以豐富用戶提供的 dataframe 的數據:
# user code using the library
import my_lib
df = pd.DataFrame(data='some data')
enriched_df = my_lib.enrich(df)
公開異步 function 通常是個好主意。這樣你會給你的用戶更多的靈活性。
如果您的某些用戶不能(或不想)對您的函數使用異步調用,他們將能夠使用asyncio.run(your_function())
調用異步 function。 或者在極少數情況下,他們正在運行事件循環但無法進行異步調用,他們可以使用此處描述的create_task
+ add_one_callback
方法。 (我真的不知道為什么會發生這樣的用例,但為了論證我把它包括在內。)
對用戶隱藏異步接口並不是最好的主意,因為它限制了他們的能力。 他們可能會分叉你的 package 來修補它並使暴露的 function 異步或直接調用隱藏的異步 function。 這些對您來說都不是好消息(更難記錄/跟蹤錯誤)。 我真的建議堅持使用最簡單的解決方案,並提供async
函數作為主要入口點。
假設以下 package 代碼后跟它的 3 種不同用法:
async def package_code():
return "package"
典型的客戶可能只會這樣使用它:
async def client_code_a():
print(await package_code())
# asyncio.run(client_code_a())
對於某些人來說,以下內容可能是有道理的。 例如,如果您的 package 是他們將使用的唯一異步對象。 或者也許他們還不習慣使用異步代碼(這些你可能會說服嘗試client_code_a
而不是):
def client_code_b():
print(asyncio.run(package_code()))
# client_code_b()
極少數(我很想說沒有):
async def client_code_c():
# asyncio.run() cannot be called from a running event loop:
# print(asyncio.run(package_code()))
loop = asyncio.get_running_loop()
task = loop.create_task(package_code())
task.add_done_callback(lambda t: print(t.result()))
# asyncio.run(client_code_c())
我仍然不確定您的目標是什么,但我將用代碼描述我在評論中試圖解釋的內容,以便您可以告訴我您的問題出在哪里。
如果你 package 要求用戶調用一些函數(示例中的your_package_function
),將協程作為 arguments,那么你不應該擔心事件循環。
這意味着 package 不應調用asyncio.run
或loop.run_until_complete
。 客戶端應該(在幾乎所有情況下)負責啟動偶數循環。
您的 package 代碼應該假設有一個事件循環正在運行。 因為我不知道你的包的目標我只是做了一個 function 為客戶端傳遞的任何協程提供一個"test"
參數:
import asyncio
async def your_package_function(coroutine):
print("- Package internals start")
task = asyncio.create_task(coroutine("test"))
await asyncio.sleep(.5) # Simulates slow tasks within your package
print("- Package internals completed other task")
x = await task
print("- Package internals end")
return x
客戶端(包用戶)然后應調用以下內容:
async def main():
x = await your_package_function(return_with_delay)
print(f"Computed value = {x}")
async def return_with_delay(value):
print("+ User function start")
await asyncio.sleep(.2)
print("+ User function end")
return value
await main()
# or asyncio.run(main()) if needed
這將打印:
- Package internals start
- Package internals completed other task
+ User function start
+ User function end
- Package internals end
Computed value = test
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.