Question: I am currently studying the differences between synchronous programming in Python and asynchronous programming using asyncio by looking at example code. While doing so, I have a question. Since my current job is a machine learning engineer, I wrote the example code using analogies to a server logic that serves machine learning. Here's the scenario I want to describe: Firstly, there exists a function called preprocessing that must always be executed first. This is because it preprocesses the input data for the two functions that follow (predict and apply_business_logic). Secondly, the output from the preprocessing function will be used as input for both the predict and apply_business_logic functions. The predict function simply generates a prediction calculated from a machine learning model, while the apply_business_logic function applies an arbitrary business logic. The key point is that these two functions are independent of each other, making asynchronous execution much more efficient than synchronous execution. Lastly, the logic involves calculating the weighted average of the outputs from the predict and apply_business_logic functions to determine the final result, which will be returned. If we assume the logic is structured into three steps as described above, executing it synchronously is very straightforward. 1. synchronous running import time def preprocess(x: list[int]): """ dummy preprocess """ return x def predict(x: list[int]) -> float: time.sleep(3) pred = sum(x) / len(x) print(">> finish `predict` function!") return pred def apply_business_logic(x: list[int]) -> int: res = sum(x) ** 2 print(">> finish `apply_business_logic` function!") return res def main(x: list[int]) -> float: # sync function because it must be done before running `predict` and `apply_business_logic` function x = preprocess(x) # asynchronously running(..?) pred_result = predict(x) logic_result = apply_business_logic(x) # final response alpha = 0.7 response = (alpha * pred_result) + ((1 - alpha) * logic_result) return response if __name__ == "__main__": x = list(range(100)) res = main(x) print('>> final result:', res)  2.asynchronous running(1) I can't ensure that below code will be asynchronously running.. import asyncio def preprocess(x: list[int]): """ dummy preprocess """ return x async def predict(x: list[int]) -> float: await asyncio.sleep(delay=3) pred = sum(x) / len(x) print(">> finish `predict` function!") return pred async def apply_business_logic(x: list[int]) -> int: res = sum(x) ** 2 print(">> finish `apply_business_logic` function!") return res async def main(x: list[int]) -> float: # sync function because it must be done before running `predict` and `apply_business_logic` function x = preprocess(x) # asynchronously running(..?) pred_result = await predict(x) logic_result = await apply_business_logic(x) # final response alpha = 0.7 response = (alpha * pred_result) + ((1 - alpha) * logic_result) return response if __name__ == "__main__": x = list(range(100)) loop = asyncio.get_event_loop() res = loop.run_until_complete(main(x)) loop.close() print('>> final result:', res)  3.asynchronous running(2) this code must be running asynchronously and I checked it. import asyncio def preprocess(x: list[int]): """ dummy preprocess """ return x async def predict(x: list[int]) -> float: await asyncio.sleep(delay=3) pred = sum(x) / len(x) print(">> finish `predict` function!") return pred async def apply_business_logic(x: list[int]) -> int: res = sum(x) ** 2 print(">> finish `apply_business_logic` function!") return res async def main(x: list[int]) -> float: # sync function because it must be done before running `predict` and `apply_business_logic` function x = preprocess(x) tasks = await asyncio.gather( predict(x), apply_business_logic(x) ) pred_result, logic_result = tasks[0], tasks[1] # final response alpha = 0.7 response = (alpha * pred_result) + ((1-alpha) * logic_result) return response if __name__ == "__main__": x = list(range(100)) resp = asyncio.run(main(x)) print("resp >>", resp)  I don't know the difference between two methods(1.synchronous running and 2.asynchronous running(1)). Especially the second method(2.asynchronous running(1)) is first printing print(">> finish predict function!") though using asynchronous grammar(await, async def keyword..). So, the second method is not asynchronous??

Answer: To make my comments an answer: async doesn't make your synchronous Python functions automagically asynchronous; async really is a form of cooperative multitasking. Here's a simplified example with f doing asynchronous work (in the form of an asyncio.sleep) and f2 doing synchronous work (in the form of time.sleep). import asyncio import time async def f(name: str) -> None: for x in range(3): print(f"{name} will sleep") await asyncio.sleep(0.5) print(f"{name} woke up!") async def f2(name: str) -> None: for x in range(3): print(f"{name} will sleep (2)") time.sleep(0.5) print(f"{name} woke up (2)!") async def main() -> float: await asyncio.gather( f("Hernekeitto"), f("Viina"), f("Teline"), ) print("----") await asyncio.gather( f2("Johannes"), f2("Appelsiini"), f2("Kuutio"), ) if __name__ == "__main__": asyncio.run(main())  This prints out the following: Hernekeitto will sleep Viina will sleep Teline will sleep Hernekeitto woke up! Hernekeitto will sleep Viina woke up! Viina will sleep Teline woke up! Teline will sleep Hernekeitto woke up! Hernekeitto will sleep Viina woke up! Viina will sleep Teline woke up! Teline will sleep Hernekeitto woke up! Viina woke up! Teline woke up! ---- Johannes will sleep (2) Johannes woke up (2)! Johannes will sleep (2) Johannes woke up (2)! Johannes will sleep (2) Johannes woke up (2)! Appelsiini will sleep (2) Appelsiini woke up (2)! Appelsiini will sleep (2) Appelsiini woke up (2)! Appelsiini will sleep (2) Appelsiini woke up (2)! Kuutio will sleep (2) Kuutio woke up (2)! Kuutio will sleep (2) Kuutio woke up (2)! Kuutio will sleep (2) Kuutio woke up (2)!  As you can see, in the first case, the three async function invocations are able to do their work in parallel (but there's always a pattern of woke up! will sleep, because there's no await between the two prints (as they're run in a loop). In the second case, the functions are run serially, since there is no await in f2 that would allow other async coroutines to run. As mentioned in the comments, you can use loop.run_in_executor() to run a synchronous function in a concurrent.futures.Executor (commonly a ThreadPoolExecutor), so it's run in a different thread (or a process), and the result can be awaited.