JavaScript ਤੋਂ Python ਵਿੱਚ ਜਾਣਾ ਇੱਕ ਅਜਿਹੇ ਸ਼ਹਿਰ ਵਿੱਚ ਜਾਣ ਵਾਂਗ ਮਹਿਸੂਸ ਹੁੰਦਾ ਹੈ ਜਿੱਥੇ ਸੜਕਾਂ ਦੇ ਨਿਸ਼ਾਨ ਤਾਂ ਉਹੀ ਹਨ ਪਰ ਟ੍ਰੈਫਿਕ ਦੇ ਨਿਯਮ ਵੱਖਰੇ ਹਨ। ਸਿੰਟੈਕਸ (syntax) ਦੋਸਤਾਨਾ ਅਤੇ ਜਾਣ ਪਛਾਣ ਵਾਲਾ ਲੱਗਦਾ ਹੈ। ਤੁਸੀਂ ਵਿਆਕਰਣ ਵਿੱਚ async ਅਤੇ await ਨੂੰ ਉੱਥੇ ਹੀ ਦੇਖਦੇ ਹੋ, ਇਸ ਲਈ ਤੁਸੀਂ ਮੰਨ ਲੈਂਦੇ ਹੋ ਕਿ ਮਾਨਸਿਕ ਮਾਡਲ (mental model) ਬਿਨਾਂ ਕਿਸੇ ਮੁਸ਼ਕਲ ਦੇ ਕੰਮ ਕਰੇਗਾ। ਪਰ ਅਜਿਹਾ ਨਹੀਂ ਹੁੰਦਾ। JavaScript ਦਾ ਇੱਕ ਆਦਤ ਬਣ ਚੁੱਕਾ ਪੈਟਰਨ ਤੁਹਾਡੀ Python ਦੀ ਪਰਫਾਰਮੈਂਸ ਨੂੰ ਬਿਨਾਂ ਕਿਸੇ ਕ੍ਰੈਸ਼ ਹੋਣ, ਬਿਨਾਂ ਕਿਸੇ ਐਰਰ (error) ਨੂੰ ਲੌਗ ਕੀਤੇ, ਅਤੇ ਬਿਨਾਂ ਕਿਸੇ ਕੋਡ ਰਿਵਿਊ ਵਿੱਚ ਦਿਖਾਈ ਦਿੱਤੇ ਚੁੱਪਚਾਪ ਖ਼ਰਾਬ ਕਰ ਦਿੰਦਾ ਹੈ।

JavaScript ਤੁਹਾਨੂੰ ਕਿਵੇਂ 'ਸ਼ੁਰੂ ਕਰਕੇ ਭੁੱਲ ਜਾਣ' ਲਈ ਸਿਖਾਉਂਦਾ ਹੈ

JavaScript ਵਿੱਚ, ਇੱਕ async ਫੰਕਸ਼ਨ ਕਾਲ 'eager' (ਤੁਰੰਤ ਕਾਰਜਸ਼ੀਲ) ਹੁੰਦੀ ਹੈ। ਜਿਸ ਪਲ ਤੁਸੀਂ ਇਸਨੂੰ ਇਵੋਕ (invoke) ਕਰਦੇ ਹੋ, ਇੰਜਣ ਇੱਕ Promise ਬਣਾਉਂਦਾ ਹੈ ਅਤੇ ਕੰਮ ਤੁਰੰਤ ਸ਼ੁਰੂ ਹੋ ਜਾਂਦਾ ਹੈ। Event loop ਪਹਿਲਾਂ ਹੀ ਕੰਮ 'ਤੇ ਲੱਗ ਚੁੱਕਾ ਹੁੰਦਾ ਹੈ। ਇਸੇ ਕਰਕੇ JavaScript ਡਿਵੈਲਪਰ ਕੁਦਰਤੀ ਤੌਰ 'ਤੇ ਇਸ ਤਰ੍ਹਾਂ ਦਾ ਕੋਡ ਲਿਖਦੇ ਹਨ:

const userPromise = fetchUser(id);
const ordersPromise = fetchOrders(id);
const user = await userPromise;
const orders = await ordersPromise;

ਦੋਵੇਂ ਨੈੱਟਵਰਕ ਰਿਕੁਐਸਟਾਂ await ਤੱਕ ਪਹੁੰਚਣ ਤੋਂ ਪਹਿਲਾਂ ਹੀ ਚੱਲ ਰਹੀਆਂ ਹੁੰਦੀਆਂ ਹਨ। ਪਹਿਲਾ await ਮੌਜੂਦਾ ਫੰਕਸ਼ਨ ਨੂੰ ਉਦੋਂ ਤੱਕ ਰੋਕ ਦਿੰਦਾ ਹੈ ਜਦੋਂ ਤੱਕ fetchUser ਰੈਜ਼ੋਲਵ (resolve) ਨਹੀਂ ਹੋ ਜਾਂਦਾ, ਪਰ fetchOrders ਪਿਛਲੀ ਲਾਈਨ ਤੋਂ ਹੀ ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲ ਰਿਹਾ ਹੁੰਦਾ ਹੈ। ਜਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ orders ਵੇਰੀਏਬਲ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ, ਦੂਜੀ ਰਿਕੁਐਸਟ ਸ਼ਾਇਦ ਪਹਿਲਾਂ ਹੀ ਪੂਰੀ ਹੋ ਚੁੱਕੀ ਹੁੰਦੀ ਹੈ। JavaScript ਵਿੱਚ ਇਹ ਪੈਟਰਨ ਇੰਨਾ ਕੁਦਰਤੀ ਲੱਗਦਾ ਹੈ ਕਿ ਬਹੁਤ ਸਾਰੇ ਡਿਵੈਲਪਰ ਇਸਨੂੰ ਕੰਕਰੈਂਸੀ (concurrency) ਦੀ ਚਾਲ ਵਜੋਂ ਵੀ ਨਹੀਂ ਦੇਖਦੇ। ਇਹ ਬੱਸ async ਦੇ ਕੰਮ ਕਰਨ ਦਾ ਤਰੀਕਾ ਹੈ।

Python ਦਾ ਹੈਰਾਨ ਕਰਨ ਵਾਲਾ ਪੱਖ: ਇੱਕ Cold Coroutine

Python ਇੱਕ ਵੱਖਰੇ ਇਕਰਾਰਨਾਮੇ (contract) ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਜਦੋਂ ਤੁਸੀਂ Python ਵਿੱਚ async def ਫੰਕਸ਼ਨ ਨੂੰ ਕਾਲ ਕਰਦੇ ਹੋ, ਤਾਂ ਤੁਸੀਂ ਕੋਈ ਕੰਮ ਸ਼ੁਰੂ ਨਹੀਂ ਕਰਦੇ। ਤੁਹਾਨੂੰ ਇੱਕ coroutine ਆਬਜੈਕਟ ਮਿਲਦਾ ਹੈ। ਇਸਨੂੰ ਕਾਗਜ਼ 'ਤੇ ਲਿਖੀ ਇੱਕ ਰੈਸਿਪੀ ਵਾਂਗ ਸਮਝੋ। ਸਮੱਗਰੀ ਦੀ ਸੂਚੀ ਹੈ, ਕਦਮ ਸਾਫ਼ ਹਨ, ਪਰ ਕੁਝ ਵੀ ਓਵਨ ਵਿੱਚ ਨਹੀਂ ਹੈ। ਜਦੋਂ ਤੱਕ ਕੋਈ ਚੀਜ਼ ਉਸ coroutine ਨੂੰ event loop ਰਾਹੀਂ ਸਪੱਸ਼ਟ ਤੌਰ 'ਤੇ ਨਹੀਂ ਚਲਾਉਂਦੀ, ਇਹ ਨਿਰਜੀਵ (inert) ਰਹਿੰਦਾ ਹੈ।

ਇੱਥੇ ਇੱਕ ਜਾਲ ਹੈ। ਇੱਕ JavaScript ਇੰਜੀਨੀਅਰ ਜਿਸਨੂੰ ਯੂਜ਼ਰ ਅਤੇ ਉਸਦੇ ਆਰਡਰਾਂ ਦੀ ਲੋੜ ਹੈ, ਉਹ Python ਵਿੱਚ ਇਹ ਲਿਖ ਸਕਦਾ ਹੈ:

user_coro = fetch_user(id)
orders_coro = fetch_orders(id)
user = await user_coro
orders = await orders_coro

ਇਹ ਕੰਕਰੈਂਟ (concurrent) ਲੱਗਦਾ ਹੈ। ਇਹ ਕੰਕਰੈਂਟ ਮਹਿਸੂਸ ਹੁੰਦਾ ਹੈ। ਪਰ ਇਹ ਪੂਰੀ ਤਰ੍ਹਾਂ ਸੀਕੁਐਂਸ਼ੀਅਲ (sequential) ਹੈ।

ਪਹਿਲੀ ਲਾਈਨ user_coro ਨੂੰ ਇੱਕ ਸੁੱਤੀ ਹੋਈ (dormant) coroutine ਅਸਾਈਨ ਕਰਦੀ ਹੈ। ਦੂਜੀ ਲਾਈਨ orders_coro ਨੂੰ ਇੱਕ ਹੋਰ ਸੁੱਤੀ ਹੋਈ coroutine ਅਸਾਈਨ ਕਰਦੀ ਹੈ। ਜਦੋਂ ਐਗਜ਼ੀਕਿਊਸ਼ਨ await user_coro 'ਤੇ ਪਹੁੰਚਦੀ ਹੈ, ਤਾਂ Python ਅੰਤ ਵਿੱਚ ਪਹਿਲਾ ਟਾਸਕ ਸ਼ੁਰੂ ਕਰਦਾ ਹੈ ਅਤੇ ਇਸਨੂੰ ਪੂਰਾ ਕਰਦਾ ਹੈ। fetch_user ਖਤਮ ਹੋਣ ਤੋਂ ਬਾਅਦ ਹੀ ਇੰਟਰਪ੍ਰੀਟਰ await orders_coro ਤੱਕ ਪਹੁੰਚਦਾ ਹੈ ਅਤੇ ਦੂਜਾ ਟਾਸਕ ਸ਼ੁਰੂ ਕਰਦਾ ਹੈ। ਤੁਹਾਡਾ ਕੁੱਲ ਐਗਜ਼ੀਕਿਊਸ਼ਨ ਸਮਾਂ ਦੋਵਾਂ I/O ਆਪਰੇਸ਼ਨਾਂ ਦਾ ਜੋੜ ਹੁੰਦਾ ਹੈ, ਨਾ ਕਿ ਸਭ ਤੋਂ ਲੰਬਾ ਸਮਾਂ। ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਪੈਰਲਲ (parallel) ਨਹੀਂ ਚਲਾਇਆ। ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਵਾਧੂ ਕਦਮਾਂ ਦੇ ਨਾਲ ਇੱਕ ਤੋਂ ਬਾਅਦ ਇੱਕ ਚਲਾਇਆ।

ਇਹ ਬੱਗ (Bug) ਅਦਿੱਖ ਕਿਉਂ ਹੈ

ਇਹ ਉਸ ਕਿਸਮ ਦੀ ਪਰਫਾਰਮੈਂਸ ਰਿਗਰੈਸ਼ਨ (performance regression) ਹੈ ਜੋ ਮਹੀਨਿਆਂ ਤੱਕ ਬਣੀ ਰਹਿੰਦੀ ਹੈ। ਕੋਡ ਵੈਲਿਡ Python ਹੈ। ਇਹ ਟਾਈਪ ਚੈਕਰਾਂ ਨੂੰ ਪਾਸ ਕਰਦਾ ਹੈ। ਇਹ ਸਹੀ ਨਤੀਜੇ ਦਿੰਦਾ ਹੈ। ਇਹ ਬੱਸ ਅੱਧੀ ਰਫ਼ਤਾਰ ਨਾਲ, ਜਾਂ ਉਸ ਤੋਂ ਵੀ ਮਾੜੀ ਚੱਲਦਾ ਹੈ। ਕਿਉਂਕਿ ਕੋਈ ਸਟੈਕ ਟ੍ਰੇਸ (stack trace) ਅਤੇ ਕੋਈ ਚੇਤਾਵਨੀ ਨਹੀਂ ਹੁੰਦੀ, ਇੰਜੀਨੀਅਰਿੰਗ ਟੀਮਾਂ ਅਕਸਰ ਪਹਿਲਾਂ ਹੋਰ ਕਿਤੇ ਦੇਖਦੀਆਂ ਹਨ। ਉਹ Redis ਕੈਸ਼ ਜੋੜਦੇ ਹਨ, ਡਾਟਾਬੇਸ ਟਾਇਰ ਅੱਪਗ੍ਰੇਡ ਕਰਦੇ ਹਨ, ਜਾਂ ਹੋਸਟਿੰਗ ਰੀਜਨ ਬਦਲ ਦਿੰਦੇ ਹਨ। ਅਸਲ ਦੋਸ਼ੀ await ਅਸਲ ਵਿੱਚ ਕੀ ਕਰਦਾ ਹੈ, ਇਸ ਬਾਰੇ ਉਮੀਦਾਂ ਵਿੱਚ ਇੱਕ ਸੂਖਮ ਅਸੰਗਤੀ ਹੈ।

Python ਨੂੰ ਅਸਲ ਵਿੱਚ ਚੀਜ਼ਾਂ ਨੂੰ ਕੰਕਰੈਂਟਲੀ ਚਲਾਉਣ ਦੇ ਤਿੰਨ ਤਰੀਕੇ

ਇਸ ਨੂੰ ਠੀਕ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ Python ਦੇ event loop ਨੂੰ ਕੰਮ ਨੂੰ ਤੁਰੰਤ ਸ਼ਡਿਊਲ ਕਰਨ ਲਈ ਕਹਿਣਾ ਚਾਹੀਦਾ ਹੈ। ਤੁਹਾਨੂੰ ਇੱਕ ਸਾਧਾਰਨ coroutine ਨਾਲੋਂ ਕੁਝ ਵਧੇਰੇ ਸਰਗਰਮ ਚੀਜ਼ ਦੀ ਲੋੜ ਹੈ। ਤੁਹਾਨੂੰ ਇੱਕ Task ਦੀ ਲੋੜ ਹੈ।

1. asyncio.create_task

JavaScript ਪੈਟਰਨ ਦਾ ਸਭ ਤੋਂ ਸਿੱਧਾ ਅਨੁਵਾਦ ਆਪਣੀ coroutine ਨੂੰ ਇੱਕ Task ਵਿੱਚ ਲਪੇਟਣਾ (wrap) ਹੈ। ਇੱਕ Task ਜਿਵੇਂ ਹੀ ਤੁਸੀਂ ਇਸਨੂੰ ਬਣਾਉਂਦੇ ਹੋ, event loop 'ਤੇ ਸ਼ਡਿਊਲ ਹੋ ਜਾਂਦਾ ਹੈ। ਇਹ ਚੱਲ ਰਹੇ JavaScript Promise ਦੇ ਸਭ ਤੋਂ ਨੇੜਲੇ Python ਬਰਾਬਰ ਹੈ।

user_task = asyncio.create_task(fetch_user(id))
orders_task = asyncio.create_task(fetch_orders(id))

user = await user_task
orders = await_orders_task

ਹੁਣ ਪਹਿਲੇ await ਤੋਂ ਪਹਿਲਾਂ fetch_user ਅਤੇ fetch_orders ਦੋਵੇਂ ਚੱਲ ਰਹੇ ਹਨ। ਜਦੋਂ ਤੁਸੀਂ await user_task 'ਤੇ ਪਹੁੰਚਦੇ ਹੋ, ਤਾਂ ਤੁਸੀਂ ਸਿਰਫ਼ ਉਦੋਂ ਤੱਕ ਰੁਕਦੇ ਹੋ ਜਦੋਂ ਤੱਕ ਉਹ ਖਾਸ Task ਪੂਰਾ ਨਹੀਂ ਹੋ ਜਾਂਦਾ, ਪਰ ਦੂਜਾ Task ਚੱਲਦਾ ਰਹਿੰਦਾ ਹੈ। ਜੇਕਰ fetch_orders ਪਹਿਲਾਂ ਖਤਮ ਹੋ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਇਸਦਾ ਨਤੀਜਾ orders_task ਦੇ ਅੰਦਰ ਉਦੋਂ ਤੱਕ ਉਡੀਕ ਕਰਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ ਨਹੀਂ ਮੰਗਦੇ।

ਹਾਲਾਂਕਿ, ਸਾਵਧਾਨ ਰਹੋ। ਜੇਕਰ ਤੁਸੀਂ ਇੱਕ Task ਬਣਾਉਂਦੇ ਹੋ ਅਤੇ ਕਦੇ ਵੀ ਉਸਨੂੰ await ਨਹੀਂ ਕਰਦੇ, ਤਾਂ Python ਇੱਕ ਵਿਨਾਸ਼ਸ਼ੀਲ (destroyed) pending task ਬਾਰੇ ਐਰਰ ਦੇਵੇਗਾ। ਤੁਹਾਨੂੰ ਅਜੇ ਵੀ ਆਪਣੇ ਨਤੀਜੇ ਇਕੱਠੇ ਕਰਨੇ ਚਾਹੀਦੇ ਹਨ।

2. asyncio.gather

ਜੇਕਰ ਤੁਹਾਡੇ ਕੋਲ ਕਈ coroutines ਹਨ ਜੋ ਅੱਗੇ ਵਧਣ ਤੋਂ ਪਹਿਲਾਂ ਸਾਰੇ ਖਤਮ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ, ਤਾਂ asyncio.gather ਤੁਹਾਡੇ ਲਈ ਬੋਇਲਰਪਲੇਟ (boilerplate) ਨੂੰ ਸੰਭਾਲ ਲੈਂਦਾ ਹੈ। ਇਹ ਅੰਦਰੂਨੀ ਤੌਰ 'ਤੇ ਹਰੇਕ coroutine ਨੂੰ ਇੱਕ Task ਵਜੋਂ ਸ਼ਡਿਊਲ ਕਰਦਾ ਹੈ ਅਤੇ ਉਹਨਾਂ ਨੂੰ ਇਕੱਠੇ await ਕਰਦਾ ਹੈ।

user, orders = await asyncio.gather(fetch_user(id), fetch_orders(id))

ਇਹ ਸੰਖੇਪ ਅਤੇ ਪੜ੍ਹਨਯੋਗ ਹੈ। ਇਹ ਉਦੋਂ ਵ

Python 3.11 ਨੇ TaskGroup ਪੇਸ਼ ਕੀਤਾ ਹੈ, ਜੋ standard library ਵਿੱਚ structured concurrency ਲੈ ਕੇ ਆਉਂਦਾ ਹੈ। Tasks ਨੂੰ ਹੱਥ ਨਾਲ ਬਣਾਉਣ ਦੀ ਬਜਾਏ, ਤੁਸੀਂ ਇੱਕ context manager ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋ ਜੋ ਇਹ ਯਕੀਨੀ ਬਣਾਉਂਦਾ ਹੈ ਕਿ ਹਰ ਬਣਾਇਆ ਗਿਆ task ਸਹੀ ਤਰੀਕੇ ਨਾਲ ਖਤਮ ਹੋਵੇ। ਜੇਕਰ ਇੱਕ task ਕੋਈ exception ਪੈਦਾ ਕਰਦਾ ਹੈ, ਤਾਂ ਬਾਕੀ ਆਪਣੇ ਆਪ cancel ਹੋ ਜਾਂਦੇ ਹਨ।

async with asyncio.TaskGroup() as tg:
    user_task = tg.create_task(fetch_user(id))
    orders_task = tg.create_task(fetch_orders(id))

user = user_task.result()
orders = orders_task.result()

ਇਹ pattern ਗੁੰਝਲਦਾਰ workflows ਲਈ ਬਹੁਤ ਵਧੀਆ ਹੈ। ਇਹ Task ਨੂੰ ਅਧੂਰਾ (orphaning) ਛੱਡਣ ਦੇ ਜੋਖਮ ਨੂੰ ਖਤਮ ਕਰਦਾ ਹੈ, ਅਤੇ ਸਬੰਧਤ operations ਦੇ lifecycle ਨੂੰ ਇੱਕ ਤਰਕਪੂਰਨ (logical) ਛੱਤਰ ਹੇਠ ਇਕੱਠਾ ਕਰਦਾ ਹੈ। ਜੇਕਰ ਤੁਹਾਡਾ codebase Python 3.11 ਜਾਂ ਇਸ ਤੋਂ ਨਵੇਂ ਵਰਜ਼ਨ 'ਤੇ ਚੱਲਦਾ ਹੈ, ਤਾਂ fan-out concurrency ਲਈ ਇਹ ਅਕਸਰ ਸਭ ਤੋਂ ਸਾਫ਼ architecture ਹੁੰਦਾ ਹੈ।

ਮਾਨਸਿਕ ਮਾਡਲ (Mental Model): await ਦਾ ਮਤਲਬ ਹੈ "ਇਸ ਨੂੰ ਹੁਣੇ ਚਲਾਓ"

ਮੁੱਖ ਸਬਕ ਭਾਸ਼ਾਈ ਹੈ। JavaScript ਵਿੱਚ, ਤੁਸੀਂ await ਨੂੰ "ਉਦੋਂ ਤੱਕ" (meanwhile) ਵਜੋਂ ਪੜ੍ਹ ਸਕਦੇ ਹੋ। ਤੁਸੀਂ ਕੰਮ ਸ਼ੁਰੂ ਕਰਦੇ ਹੋ, ਹੋਰ ਕੰਮ ਕਰਦੇ ਹੋ, ਅਤੇ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਰੁਕਦੇ ਹੋ ਜਦੋਂ ਤੁਹਾਨੂੰ ਉਸ value ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। Python ਵਿੱਚ, await ਦਾ ਮਤਲਬ ਹੈ "ਇਸ coroutine ਨੂੰ ਇਸਦੇ ਅਗਲੇ suspension point ਜਾਂ completion ਤੱਕ ਲੈ ਕੇ ਜਾਓ।" ਜੇਕਰ coroutine ਅਜੇ ਤੱਕ schedule ਨਹੀਂ ਕੀਤੀ ਗਈ ਹੈ, ਤਾਂ await ਹੀ ਇਸਨੂੰ schedule ਕਰਦਾ ਹੈ। ਇਸੇ ਕਰਕੇ ਤੁਸੀਂ ਦੋ raw coroutines ਸ਼ੁਰੂ ਨਹੀਂ ਕਰ ਸਕਦੇ ਅਤੇ ਫਿਰ ਬਾਅਦ ਵਿੱਚ ਉਹਨਾਂ ਨੂੰ await ਨਹੀਂ ਕਰ ਸਕਦੇ। ਤੁਸੀਂ ਇਸ ਦੌਰਾਨ event loop ਨੂੰ ਕਰਨ ਲਈ ਕੁਝ ਨਹੀਂ ਦਿੱਤਾ ਹੈ।

Python coroutines ਨੂੰ generator functions ਵਾਂਗ ਸਮਝੋ। Generator ਨੂੰ ਕਾਲ ਕਰਨ ਨਾਲ ਇਹ iterate ਨਹੀਂ ਹੁੰਦਾ। ਤੁਹਾਨੂੰ ਇਸ ਉੱਤੇ loop ਚਲਾਉਣ, next() ਕਾਲ ਕਰਨ, ਜਾਂ ਇਸਨੂੰ ਕਿਸੇ consumer ਨੂੰ pass ਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। Async ਵੀ ਇਸੇ ਤਰ੍ਹਾਂ ਕੰਮ ਕਰਦਾ ਹੈ। asyncio.create_task ਉਹ consumer ਹੈ ਜੋ ਕਹਿੰਦਾ ਹੈ "ਇਸ ਨੂੰ ਹੁਣੇ event loop 'ਤੇ ਪਾ ਦਿਓ।" ਇਸ ਤੋਂ ਬਾਅਦ ਵਾਲਾ await ਸਿਰਫ਼ finished signal ਦੀ ਉਡੀਕ ਕਰਦਾ ਹੈ।

ਇੱਕ ਵਿਸ਼ੇਸ਼ ਆਦਤ ਜੋ ਮਦਦ ਕਰਦੀ ਹੈ: ਜਦੋਂ ਵੀ ਤੁਸੀਂ await ਤੋਂ ਬਿਨਾਂ ਕਿਸੇ async function call ਨੂੰ ਇੱਕ variable ਨੂੰ assign ਕਰਦੇ ਹੋ, ਤਾਂ ਆਪਣੇ ਆਪ ਨੂੰ ਪੁੱਛੋ ਕਿ ਕੀ ਤੁਸੀਂ ਇਸਨੂੰ schedule ਕੀਤਾ ਹੈ। ਜੇਕਰ ਸੱਜੇ ਪਾਸੇ create_task, gather, ਜਾਂ TaskGroup ਵਿੱਚ ਲਪੇਟਿਆ (wrapped) ਨਹੀਂ ਗਿਆ ਹੈ, ਤਾਂ ਇਹ ਚੱਲ ਨਹੀਂ ਰਿਹਾ ਹੈ। ਇਹ ਸਿਰਫ਼ ਕਾਊਂਟਰ 'ਤੇ ਰੱਖੀ ਇੱਕ ਰੈਸਿਪੀ (recipe) ਵਾਂਗ ਹੈ।

ਸਿੱਖਿਆ (Takeaway)

Python ਦਾ async runtime ਸ਼ਕਤੀਸ਼ਾਲੀ ਹੈ, ਪਰ ਇਹ ਸਪੱਸ਼ਟ ਇਰਾਦੇ (explicit intent) ਦੀ ਮੰਗ ਕਰਦਾ ਹੈ। ਭਾਸ਼ਾ ਸਿਰਫ਼ ਇਸ ਲਈ background ਕੰਮ ਸ਼ੁਰੂ ਨਹੀਂ ਕਰਦੀ ਕਿਉਂਕਿ ਤੁਸੀਂ ਇੱਕ function ਨੂੰ ਕਾਲ ਕੀਤਾ ਹੈ। ਜੇਕਰ ਤੁਸੀਂ JavaScript ਤੋਂ ਆ ਰਹੇ ਹੋ, ਤਾਂ ਹਰ ਉਸ ਜਗ੍ਹਾ ਦੀ ਜਾਂਚ (audit) ਕਰੋ ਜਿੱਥੇ ਤੁਸੀਂ ਇੱਕ coroutine ਨੂੰ variable ਵਿੱਚ ਸਟੋਰ ਕਰਦੇ ਹੋ ਅਤੇ ਬਾਅਦ ਵਿੱਚ ਉਸਨੂੰ await ਕਰਦੇ ਹੋ। ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਪਹਿਲਾਂ ਇਸਨੂੰ Task ਵਿੱਚ ਨਹੀਂ ਬਦਲਿਆ, ਤੁਸੀਂ async ਦੇ ਭੇਸ ਵਿੱਚ sequential code ਲਿਖਿਆ ਹੈ। ਕੰਮ ਨੂੰ ਇੱਕ Task ਨਾਲ ਸ਼ੁਰੂ ਕਰੋ, ਫਿਰ ਨਤੀਜਿਆਂ ਦੀ ਉਡੀਕ ਕਰੋ। ਇਸ ਤਰ੍ਹਾਂ ਹੀ ਤੁਸੀਂ Python async ਨੂੰ ਇੱਕ ਚੁੱਪ bottleneck ਤੋਂ ਇੱਕ ਅਸਲੀ concurrency tool ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।