Keywords: Python asyncio | loop.run_until_complete | asynchronous programming
Abstract: Based on Python official documentation and community Q&A, this article delves into the principles, application scenarios, and differences between loop.run_until_complete() and ensure_future() in the asyncio event loop. Through detailed code examples, it analyzes how run_until_complete() manages coroutine execution order, explains why official examples frequently use this method, and provides best practice recommendations for real-world development. The article also discusses the fundamental differences between HTML tags like <br> and character \n.
Introduction and Background
In Python's asynchronous programming framework asyncio, the event loop is a core component responsible for scheduling and executing coroutines. Developers often wonder: why do most official documentation and example codes use loop.run_until_complete() instead of asyncio.ensure_future() combined with loop.run_forever()? This article provides an in-depth technical analysis of this issue, based on the asyncio library in Python 3.x, referencing community best answers, reorganizing logical structures, and offering comprehensive explanations and practical examples.
Basic Principles of run_until_complete()
loop.run_until_complete() is a blocking method that runs the event loop until the passed future (or coroutine) completes. This means that after calling this method, subsequent code will not execute until the future finishes. However, this does not prevent the event loop from concurrently executing other scheduled tasks internally. For example, consider the following code:
import asyncio
async def do_io():
print('io start')
await asyncio.sleep(5)
print('io end')
async def do_other_things():
print('doing other things')
loop = asyncio.get_event_loop()
loop.run_until_complete(do_io())
loop.run_until_complete(do_other_things())
loop.close()In this example, do_io() runs first, outputting "io start", then waits for 5 seconds, and outputs "io end". After that, do_other_things() executes, outputting "doing other things". Thus, the two coroutines run sequentially without concurrency. This explains why run_until_complete() might be perceived as "blocking", but it does start the event loop.
ensure_future() and Concurrent Execution
In contrast, asyncio.ensure_future() is used to wrap a coroutine into a future object and schedule it to the event loop, but it does not run immediately. To achieve concurrency, it typically requires combining with loop.run_forever() or asyncio.gather(). For instance, modifying the above code:
loop.create_task(do_other_things())
loop.run_until_complete(do_io())Here, loop.create_task() (similar to ensure_future()) schedules do_other_things() as a task, then run_until_complete(do_io()) runs the event loop until do_io() completes. Since do_other_things() is scheduled in advance, when do_io() pauses at await asyncio.sleep(5), the event loop switches to do_other_things(), outputting "doing other things", then returns to complete do_io(). The final output order is: "doing other things", "io start", "io end". This demonstrates the advantage of non-blocking concurrent execution.
Why Official Examples Prefer run_until_complete()
According to Python documentation and community discussions, run_until_complete() is widely used in examples primarily due to its simplicity and educational purpose. For beginners, it provides a clear entry point to run a single coroutine, avoiding complex event loop management. Moreover, in simple scenarios, such as running a main coroutine, run_until_complete() is efficient enough. However, in practical applications, when multiple coroutines need to run concurrently, ensure_future() or asyncio.gather() are more appropriate. For example, using asyncio.gather() can run multiple coroutines simultaneously:
async def main():
await asyncio.gather(do_io(), do_other_things())
loop.run_until_complete(main())This ensures that do_io() and do_other_things() execute concurrently, with outputs potentially interleaved. Therefore, the choice of method depends on specific needs: run_until_complete() is suitable for simple, sequential tasks, while the ensure_future() series of methods are better for complex, concurrent scenarios.
Best Practices and Conclusion
When developing asyncio applications, it is recommended to choose methods based on task nature. For independent main coroutines, use run_until_complete(); for multiple coroutines requiring concurrency, use asyncio.ensure_future(), loop.create_task(), or asyncio.gather(). Note that run_until_complete() blocks the current thread, so in GUI or server applications, it may need to be combined with run_forever(). In summary, understanding the scheduling mechanism of the event loop is key. By appropriately using these methods, developers can fully leverage the potential of Python asynchronous programming to enhance application performance.