import asyncio import collections import time # Utility function for printing relative timestamps start = None def ts(): global start if start is None: start = time.time() return round(time.time() - start, 3) async def ayield(): print(f'{ts()}: Yielding...') await asyncio.sleep(0) async def process_tasks(): """ We have a list of tasks, we process each of them. However, before starting a new task and after scheduling a new task we check that no already-running task can continue instead. We do this by yielding control (asyncio.sleep(0)) so that any 'returned' await statement will be handled first. """ q = collections.deque(range(1, 8)) print(q) while len(q): await ayield() # Before creating a task, make sure we treat ready ongoing coros first t = q.popleft() ########################################################### # Add an await ayield() here to see a change in behavior ########################################################### print(f'{ts()}: Creating task {t}') asyncio.create_task(coro(t)) await ayield() async def coro(i): print(f'{ts()}: Task {i} started') time.sleep(0.8) # Compute print(f'{ts()}: Task {i} computed. Going async') await asyncio.sleep(0.5) # async stuff print(f'{ts()}: Task {i} returned from async') time.sleep(0.2) print(f'{ts()}: Task {i} finished') await ayield() async def amain(): print(f'{ts()}: Started (Yield)') await process_tasks() print(f'{ts()}: Finished (Yield)') if __name__ == '__main__': asyncio.run(amain())