Looking at the source for asyncio.run will give you your answer:
def run(main, *, debug=False):
"""Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous
generators.
This function cannot be called when another asyncio event loop is
running in the same thread.
If debug is True, the event loop will be run in debug mode.
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
Example:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
"""
if events._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
if not coroutines.iscoroutine(main):
raise ValueError("a coroutine was expected, got {!r}".format(main))
loop = events.new_event_loop()
try:
events.set_event_loop(loop)
loop.set_debug(debug)
return loop.run_until_complete(main)
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
loop.run_until_complete(loop.shutdown_default_executor())
finally:
events.set_event_loop(None)
loop.close()
As you can see, it throws an Exception if a loop is already running in the current thread, then creates its own loop and calls run_until_complete on it. The other additional parts compared to your second code snippet is that it validates if the function you passed to it is a coroutine, and that it handles shutting everything down cleanly after the coroutine you passed to it has completed.
As its docstring says, it's intended to be used as a entry point into asyncio applications.
asyncio.rundocumentation links to the source code, which obviously many things differently. Are you interested in the actual differences, or just whether you should useasyncio.runin place of the older pattern?asyncio.runsets up a fresh event loop, whereasrun_until_completecan be called multiple times with different awaitables under the same loop, possibly reusing background tasks and other loop-specific state.asyncio.runis almost certainly what you want;run_until_completeis now considered a low-level API for specialized purposes.