同步等待异步协同程序
如果一个异步事件循环已经通过调用loop.run_forever运行,它将阻塞执行线程,直到loop.stop被调用[请参阅docs]。因此,同步等待的唯一方法是在一个专用线程上运行事件循环,在循环上调度异步函数,然后从另一个线程同步地等待它。在
为此,我按照用户4815162342的answer编写了自己的最小解决方案。我还添加了在所有工作完成后清理循环的部分[参见^{}]。在
下面代码中的main函数在一个专用线程上运行事件循环,调度事件循环上的多个任务,以及同步等待结果的任务。同步等待将阻塞,直到所需的结果准备就绪。最后,循环被关闭,并优雅地与其线程一起清理。在
专用线程和函数stop_loop、run_forever_safe、和{}可以封装在模块或类中。在
有关线程安全的注意事项,请参阅asyncio docs中的“Concurrency and Multithreading”部分。在import asyncio
import threading
#
def stop_loop(loop):
''' stops an event loop '''
loop.stop()
print (".: LOOP STOPPED:", loop.is_running())
def run_forever_safe(loop):
''' run a loop for ever and clean up after being stopped '''
loop.run_forever()
# NOTE: loop.run_forever returns after calling loop.stop
# cancell all tasks and close the loop gracefully
print(".: CLOSING LOOP...")
# source:
loop_tasks_all = asyncio.Task.all_tasks(loop=loop)
for task in loop_tasks_all: task.cancel()
# NOTE: `cancel` does not guarantee that the Task will be cancelled
for task in loop_tasks_all:
if not (task.done() or task.cancelled()):
try:
# wait for task cancellations
loop.run_until_complete(task)
except asyncio.CancelledError: pass
#END for
print(".: ALL TASKS CANCELLED.")
loop.close()
print(".: LOOP CLOSED:", loop.is_closed())
def await_sync(task):
''' synchronously waits for a task '''
while not task.done(): pass
print(".: AWAITED TASK DONE")
return task.result()
#
async def asyncTask(loop, k):
''' asynchronous task '''
print(" start async task %s" % k)
await asyncio.sleep(3, loop=loop)
print(" end async task %s." % k)
key = "KEY#%s" % k
return key
def main():
loop = asyncio.new_event_loop() # construct a new event loop
# closures for running and stopping the event-loop
run_loop_forever = lambda: run_forever_safe(loop)
close_loop_safe = lambda: loop.call_soon_threadsafe(stop_loop, loop)
# make dedicated thread for running the event loop
thread = threading.Thread(target=run_loop_forever)
# add some tasks along with my particular task
myTask = asyncio.run_coroutine_threadsafe(asyncTask(loop, 100200300), loop=loop)
otherTasks = [asyncio.run_coroutine_threadsafe(asyncTask(loop, i), loop=loop)
for i in range(1, 10)]
# begin the thread to run the event-loop
print(".: EVENT-LOOP THREAD START")
thread.start()
# _synchronously_ wait for the result of my task
result = await_sync(myTask) # blocks until task is done
print("* final result of my task:", result)
#... do lots of work ...
print("*** ALL WORK DONE ***")
#========================================
# close the loop gracefully when everything is finished
close_loop_safe()
thread.join()
#
main()