Reputation: 522
First of all I'm running this on jupyter notebook and that might be the cause of my confusion.
I have a function foo
that does some IO operations:
def foo():
# Dom some stuff
time.sleep(1.)
return 'Finished'
I want to be able to run this function in the background, so I decided to use run_in_executor
:
future = asyncio.get_event_loop().run_in_executor(None, foo)
Now the operation is non blocking and it's all fine, but if this future returns an error I want to catch it and stop the main program, so I decided to write a loop that keeps checking for the status of this future:
while True:
time.sleep(0.1)
if future.done():
# Do some stuff, like raising any caught exception
The problem is that the future status is never being changed, inside the loop it's always pending.
If instead of checking in the loop I manually check the status of the future (in the jupyter notebook) it's going to be correctly marked as finished. This behavior confuses me...
How can I keep checking for the status of a future inside a loop formulation?
Upvotes: 3
Views: 3600
Reputation: 154846
run_in_executor
is designed for use in asyncio, so its return value is an asyncio Future
, which is manipulated by the thread that runs the asyncio event loop. Since your code is spinning in a while
loop without awaiting anything, it is not giving the event loop a chance to run at all, effectively blocking the event loop. The future remains "pending" because the callback that would update is supposed to be called by the event loop, which is currently not operational - it just sits in a queue.
Replacing time.sleep(0.1)
with await asyncio.sleep(0.1)
would probably fix the issue. But then you don't need the while
loop at all; since as an asyncio future is awaitable, you can await
it directly:
await future
# Do some stuff, with the future done.
await
suspends the current coroutine until the future is complete, giving other tasks a chance to run in the meantime. It returns the value of the future, or propagates the exception.
The alternative is not to use asyncio at all, but use concurrent.futures
directly. That way you get the expected threading semantics (true "background" execution) and a Future
that works accordingly.
# keep this in a global variable, so that the same executor
# is reused for multiple calls
executor = concurrent.futures.ThreadPoolExecutor()
# later, submit foo to be executed in the background
future = executor.submit(foo)
This future is a concurrent.futures
future which supports waiting for result like this:
result = future.result()
Also, with this kind of future your original code would work unchanged.
Upvotes: 3