My question is more or less like this, which is really an X-Y problem leading back to this. This is, however, not a duplicate, because my use case is slightly different and the linked threads don't answer my question.
I am porting a set of synchronous programs from Java to Python. These programs interact with an asynchronous library. In Java, I could block and wait for this library's asynchronous functions to return a value and then do things with that value.
Here's a code sample to illustrate the problem.
def do_work_sync_1(arg1, arg2, arg3):
# won't even run because await has to be called from an async function
value = await do_work_async(arg1, arg2, arg3)
def do_work_sync_2(arg1, arg2, arg3):
# throws "Loop already running" error because the async library referenced in do_work_async is already using my event loop
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(do_work_async(arg1, arg2, arg3))
def do_work_sync_3(arg1, arg2, arg3):
# throws "got Future attached to a different loop" because the do_work_async refers back to the asynchronous library, which is stubbornly attached to my main loop
thread_pool = ThreadPoolExecutor()
future = thread_pool.submit(asyncio.run, do_work_async(arg1, arg2, arg3)
result = future.result()
def do_work_sync_4(arg1, arg2, arg3):
# just hangs forever
event_loop = asyncio.get_event_loop()
future = asyncio.run_coroutine_threadsafe(do_work_async(arg1, arg2, arg3), event_loop)
return_value = future.result()
async def do_work_async(arg1, arg2, arg3):
value_1 = await async_lib.do_something(arg1)
value_2 = await async_lib.do_something_else(arg2, arg3)
return value_1 + value_2
Python appears to be trying very hard to keep me from blocking anything, anywhere. await
can only be used from async def
functions, which in their turn must be await
ed. There doesn't seem to be a built-in way to keep async def
/await
from spreading through my code like a virus.
Task
s and Future
s don't have any built-in blocking or wait_until_complete
mechanisms unless I want to loop on Task.done()
, which seems really bad.
I tried asyncio.get_event_loop().run_until_complete()
, but that produces an error: This event loop is already running.
Apparently I'm not supposed to do that for anything except main()
.
The second linked question above suggests using a separate thread and wrapping the async function in that. I tested this with a few simple functions and it seems to work as a general concept. The problem here is that my asynchronous library keeps a reference to the main thread's event loop and throws an error when I try to refer to it from the new thread: got Future <Future pending> attached to a different loop
.
I considered moving all references to the asynchronous library into a separate thread, but I realized that I still can't block in the new thread, and I'd have to create a third thread for blocking calls, which would bring me back to the Future attached to a different loop
error.
I'm pretty much out of ideas here. Is there a way to block and wait for an async function to return, or am I really being forced to convert my entire program to async
/await
? (If it's the latter, an explanation would be nice. I don't get it.)