পাইথনে কনকারেন্ট প্রোগ্রামিংয়ের ক্ষমতা উন্মোচন করুন। উচ্চ-পারফরম্যান্স, স্কেলেবল অ্যাপ্লিকেশন তৈরির জন্য Asyncio টাস্ক তৈরি, পরিচালনা এবং বাতিল করার পদ্ধতি শিখুন।
পাইথন Asyncio-তে দক্ষতা অর্জন: টাস্ক তৈরি এবং ব্যবস্থাপনার গভীরে অনুসন্ধান
আধুনিক সফটওয়্যার ডেভেলপমেন্টের জগতে, পারফরম্যান্স অত্যন্ত গুরুত্বপূর্ণ। অ্যাপ্লিকেশনগুলি প্রতিক্রিয়াশীল হবে বলে আশা করা হয়, হাজার হাজার কনকারেন্ট নেটওয়ার্ক সংযোগ, ডাটাবেস কোয়েরি এবং API কল সহজে পরিচালনা করবে। I/O-বাউন্ড অপারেশনগুলির জন্য—যেখানে প্রোগ্রাম তার বেশিরভাগ সময় একটি নেটওয়ার্ক বা ডিস্কের মতো বাহ্যিক সংস্থানগুলির জন্য অপেক্ষা করে কাটায়—ঐতিহ্যবাহী সিঙ্ক্রোনাস কোড একটি উল্লেখযোগ্য বাধা হয়ে উঠতে পারে। এখানেই অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং তার ক্ষমতা দেখায়, এবং পাইথনের asyncio
লাইব্রেরি এই ক্ষমতাকে উন্মোচন করার চাবিকাঠি।
asyncio
-এর কনকারেন্সি মডেলের মূলে রয়েছে একটি সহজ কিন্তু শক্তিশালী ধারণা: দ্য টাস্ক। যখন কো-রুটিনগুলি কী করতে হবে তা সংজ্ঞায়িত করে, তখন টাস্কগুলি আসলে কাজগুলি সম্পন্ন করে। এগুলি কনকারেন্ট এক্সিকিউশনের মৌলিক একক, যা আপনার পাইথন প্রোগ্রামগুলিকে একাধিক অপারেশন একই সাথে পরিচালনা করতে দেয়, যার ফলে থ্রুপুট এবং প্রতিক্রিয়াশীলতা নাটকীয়ভাবে উন্নত হয়।
এই ব্যাপক নির্দেশিকা আপনাকে asyncio.Task
-এর গভীরে নিয়ে যাবে। আমরা তৈরি করার মৌলিক বিষয়গুলি থেকে শুরু করে উন্নত ব্যবস্থাপনার ধরণ, বাতিলকরণ এবং সেরা অনুশীলনগুলি সহ সবকিছু অন্বেষণ করব। আপনি একটি হাই-ট্রাফিক ওয়েব পরিষেবা, একটি ডেটা স্ক্র্যাপিং টুল বা একটি রিয়েল-টাইম অ্যাপ্লিকেশন তৈরি করুন না কেন, টাস্কগুলিতে দক্ষতা অর্জন করা যে কোনও আধুনিক পাইথন ডেভেলপারের জন্য একটি অপরিহার্য দক্ষতা।
কো-রুটিন কী? একটি দ্রুত পর্যালোচনা
দৌড়ানোর আগে আমাদের হাঁটতে শিখতে হবে। আর asyncio
-এর জগতে, হাঁটাটা হল কো-রুটিন বোঝা। একটি কো-রুটিন হল async def
দিয়ে সংজ্ঞায়িত একটি বিশেষ ধরণের ফাংশন।
যখন আপনি একটি নিয়মিত পাইথন ফাংশনকে কল করেন, তখন এটি শুরু থেকে শেষ পর্যন্ত এক্সিকিউট হয়। তবে, যখন আপনি একটি কো-রুটিন ফাংশনকে কল করেন, তখন এটি অবিলম্বে এক্সিকিউট হয় না। পরিবর্তে, এটি একটি কো-রুটিন অবজেক্ট প্রদান করে। এই অবজেক্টটি সম্পন্ন করার জন্য একটি ব্লুপ্রিন্ট, কিন্তু এটি নিজে থেকে নিষ্ক্রিয়। এটি একটি বিরতি দেওয়া গণনা যা শুরু করা, স্থগিত করা এবং পুনরায় শুরু করা যেতে পারে।
import asyncio
async def say_hello(name: str):
print(f"Preparing to greet {name}...")
await asyncio.sleep(1) # Simulate a non-blocking I/O operation
print(f"Hello, {name}!")
# Calling the function doesn't run it, it creates a coroutine object
coro = say_hello("World")
print(f"Created a coroutine object: {coro}")
# To actually run it, you need to use an entry point like asyncio.run()
# asyncio.run(coro)
জাদুর কীওয়ার্ডটি হল await
। এটি ইভেন্ট লুপকে বলে, "এই অপারেশনটি কিছুটা সময় নিতে পারে, তাই নির্দ্বিধায় আমাকে এখানে থামিয়ে অন্য কিছুতে কাজ শুরু করুন। এই অপারেশনটি সম্পূর্ণ হলে আমাকে জাগিয়ে তুলুন।" প্রসঙ্গ বিরতি এবং পরিবর্তন করার এই ক্ষমতাই কনকারেন্সি সক্ষম করে।
কনকারেন্সির মূল কেন্দ্র: asyncio.Task বোঝা
সুতরাং, একটি কো-রুটিন একটি ব্লুপ্রিন্ট। আমরা রান্নাঘরকে (ইভেন্ট লুপ) রান্না শুরু করতে কিভাবে বলব? এখানেই asyncio.Task
আসে।
একটি asyncio.Task
হল একটি অবজেক্ট যা একটি কো-রুটিনকে মোড়কে রাখে এবং এটিকে asyncio ইভেন্ট লুপে এক্সিকিউশনের জন্য শিডিউল করে। এভাবে চিন্তা করুন:
- কো-রুটিন (
async def
): একটি ডিশের জন্য একটি বিস্তারিত রেসিপি। - ইভেন্ট লুপ: কেন্দ্রীয় রান্নাঘর যেখানে সমস্ত রান্না হয়।
await my_coro()
: আপনি রান্নাঘরে দাঁড়িয়ে নিজে ধাপে ধাপে রেসিপি অনুসরণ করেন। ডিশ সম্পূর্ণ না হওয়া পর্যন্ত আপনি অন্য কিছু করতে পারবেন না। এটি হল সিকোয়েন্সিয়াল এক্সিকিউশন।asyncio.create_task(my_coro())
: আপনি রান্নাঘরের একজন শেফের (টাস্ক) কাছে রেসিপিটি দেন এবং বলেন, "এটি নিয়ে কাজ শুরু করুন।" শেফ অবিলম্বে শুরু করে, এবং আপনি অন্য কাজ করতে স্বাধীন থাকেন, যেমন আরও রেসিপি বিতরণ করা। এটি হল কনকারেন্ট এক্সিকিউশন।
মূল পার্থক্য হল যে asyncio.create_task()
কো-রুটিনকে "ব্যাকগ্রাউন্ডে" চালানোর জন্য শিডিউল করে এবং অবিলম্বে আপনার কোডে নিয়ন্ত্রণ ফিরিয়ে দেয়। আপনি একটি Task
অবজেক্ট ফিরে পান, যা এই চলমান অপারেশনের একটি হ্যান্ডেল হিসাবে কাজ করে। আপনি এর অবস্থা পরীক্ষা করতে, এটি বাতিল করতে বা পরে এর ফলাফলের জন্য অপেক্ষা করতে এই হ্যান্ডেলটি ব্যবহার করতে পারেন।
আপনার প্রথম টাস্ক তৈরি করা: `asyncio.create_task()` ফাংশন
একটি টাস্ক তৈরি করার প্রাথমিক উপায় হল asyncio.create_task()
ফাংশন ব্যবহার করা। এটি তার আর্গুমেন্ট হিসাবে একটি কো-রুটিন অবজেক্ট নেয় এবং এটিকে এক্সিকিউশনের জন্য শিডিউল করে।
মৌলিক সিনট্যাক্স
এর ব্যবহার সহজ:
import asyncio
async def my_background_work():
print("Starting background work...")
await asyncio.sleep(2)
print("Background work finished.")
return "Success"
async def main():
print("Main function started.")
# Schedule my_background_work to run concurrently
task = asyncio.create_task(my_background_work())
# While the task runs, we can do other things
print("Task created. Main function continues to run.")
await asyncio.sleep(1)
print("Main function did some other work.")
# Now, wait for the task to complete and get its result
result = await task
print(f"Task completed with result: {result}")
asyncio.run(main())
লক্ষ্য করুন যে আউটপুট দেখায় যে `main` ফাংশন টাস্ক তৈরি করার পরপরই তার এক্সিকিউশন চালিয়ে যায়। এটি ব্লক করে না। এটি শুধুমাত্র তখনই বিরতি দেয় যখন আমরা স্পষ্টভাবে `await task` ব্যবহার করি।
একটি ব্যবহারিক উদাহরণ: কনকারেন্ট ওয়েব রিকোয়েস্ট
আসুন একটি সাধারণ পরিস্থিতিতে টাস্কের আসল ক্ষমতা দেখি: একাধিক URL থেকে ডেটা আনা। এর জন্য, আমরা জনপ্রিয় `aiohttp` লাইব্রেরি ব্যবহার করব, যা আপনি `pip install aiohttp` দিয়ে ইনস্টল করতে পারেন।
প্রথমে, সিকোয়েন্সিয়াল (ধীর) পদ্ধতিটি দেখি:
import asyncio
import aiohttp
import time
async def fetch_status(session, url):
async with session.get(url) as response:
return response.status
async def main_sequential():
urls = [
"https://www.python.org",
"https://www.google.com",
"https://www.github.com",
"https://www.microsoft.com"
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
for url in urls:
status = await fetch_status(session, url)
print(f"Status for {url}: {status}")
end_time = time.time()
print(f"Sequential execution took {end_time - start_time:.2f} seconds")
# To run this, you would use: asyncio.run(main_sequential())
যদি প্রতিটি রিকোয়েস্টে প্রায় 0.5 সেকেন্ড সময় লাগে, তাহলে মোট সময় প্রায় 2 সেকেন্ড হবে, কারণ প্রতিটি `await` লুপটিকে ব্লক করে যতক্ষণ না সেই একক রিকোয়েস্ট শেষ হয়।
এখন, টাস্ক ব্যবহার করে কনকারেন্সির ক্ষমতা উন্মোচন করা যাক:
import asyncio
import aiohttp
import time
# fetch_status coroutine remains the same
async def fetch_status(session, url):
async with session.get(url) as response:
return response.status
async def main_concurrent():
urls = [
"https://www.python.org",
"https://www.google.com",
"https://www.github.com",
"https://www.microsoft.com"
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
# Create a list of tasks, but don't await them yet
tasks = [asyncio.create_task(fetch_status(session, url)) for url in urls]
# Now, wait for all tasks to complete
statuses = await asyncio.gather(*tasks)
for url, status in zip(urls, statuses):
print(f"Status for {url}: {status}")
end_time = time.time()
print(f"Concurrent execution took {end_time - start_time:.2f} seconds")
asyncio.run(main_concurrent())
যখন আপনি কনকারেন্ট সংস্করণটি চালান, তখন আপনি একটি নাটকীয় পার্থক্য দেখতে পাবেন। মোট সময় হবে প্রায় দীর্ঘতম একক রিকোয়েস্টের সময়, সবগুলির যোগফল নয়। এর কারণ হল প্রথম `fetch_status` কো-রুটিন তার `await session.get(url)`-এ পৌঁছানোর সাথে সাথেই ইভেন্ট লুপ এটিকে বিরতি দেয় এবং অবিলম্বে পরেরটি শুরু করে। সমস্ত নেটওয়ার্ক রিকোয়েস্ট কার্যকরভাবে একই সময়ে ঘটে।
টাস্কের একটি গ্রুপ পরিচালনা করা: অপরিহার্য প্যাটার্ন
স্বতন্ত্র টাস্ক তৈরি করা দুর্দান্ত, কিন্তু বাস্তব-বিশ্বের অ্যাপ্লিকেশনগুলিতে, আপনার প্রায়শই তাদের একটি পুরো গ্রুপ চালু, পরিচালনা এবং সিঙ্ক্রোনাইজ করার প্রয়োজন হয়। `asyncio` এর জন্য বেশ কিছু শক্তিশালী টুল সরবরাহ করে।
আধুনিক পদ্ধতি (Python 3.11+): `asyncio.TaskGroup`
Python 3.11-এ প্রবর্তিত, `TaskGroup` হল সম্পর্কিত টাস্কগুলির একটি গ্রুপ পরিচালনা করার জন্য নতুন, প্রস্তাবিত এবং নিরাপদ উপায়। এটি স্ট্রাকচার্ড কনকারেন্সি নামে পরিচিত একটি বৈশিষ্ট্য সরবরাহ করে।
`TaskGroup`-এর মূল বৈশিষ্ট্য:
- নিশ্চিত পরিষ্করণ (Guaranteed Cleanup): `async with` ব্লকটি এর মধ্যে তৈরি সমস্ত টাস্ক সম্পূর্ণ না হওয়া পর্যন্ত শেষ হবে না।
- দৃঢ় ত্রুটি পরিচালনা (Robust Error Handling): গ্রুপের মধ্যে কোনো টাস্ক যদি একটি ব্যতিক্রম উত্থাপন করে, তবে গ্রুপের অন্যান্য সমস্ত টাস্ক স্বয়ংক্রিয়ভাবে বাতিল হয়ে যায়, এবং `async with` ব্লক থেকে বেরোনোর সময় ব্যতিক্রম (অথবা একটি `ExceptionGroup`) পুনরায় উত্থাপিত হয়। এটি অনাথ টাস্ক প্রতিরোধ করে এবং একটি অনুমানযোগ্য অবস্থা নিশ্চিত করে।
এটি কিভাবে ব্যবহার করবেন তা এখানে:
import asyncio
async def worker(delay):
print(f"Worker starting, will sleep for {delay}s")
await asyncio.sleep(delay)
# This worker will fail
if delay == 2:
raise ValueError("Something went wrong in worker 2")
print(f"Worker with delay {delay} finished")
return f"Result from {delay}s"
async def main():
print("Starting main with TaskGroup...")
try:
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(worker(1))
task2 = tg.create_task(worker(2)) # This one will fail
task3 = tg.create_task(worker(3))
print("Tasks created in the group.")
# This part of the code will NOT be reached if an exception occurs
# The results would be accessed via task1.result(), etc.
print("All tasks completed successfully.")
except* ValueError as eg: # Note the `except*` for ExceptionGroup
print(f"Caught an exception group with {len(eg.exceptions)} exceptions.")
for exc in eg.exceptions:
print(f" - {exc}")
print("Main function finished.")
asyncio.run(main())
যখন আপনি এটি চালান, তখন আপনি দেখতে পাবেন যে `worker(2)` একটি ত্রুটি উত্থাপন করে। `TaskGroup` এটি ধরে ফেলে, অন্যান্য চলমান টাস্কগুলি (যেমন `worker(3)`) বাতিল করে এবং তারপর `ValueError` ধারণকারী একটি `ExceptionGroup` উত্থাপন করে। এই প্যাটার্নটি নির্ভরযোগ্য সিস্টেম তৈরির জন্য অবিশ্বাস্যভাবে শক্তিশালী।
ক্লাসিক কর্মক্ষমতা: `asyncio.gather()`
`TaskGroup`-এর আগে, `asyncio.gather()` ছিল একাধিক অপেক্ষাযোগ্য (awaitables) একসাথে চালানোর এবং সেগুলির সবগুলি শেষ হওয়ার জন্য অপেক্ষা করার সবচেয়ে সাধারণ উপায়।
gather()
কো-রুটিন বা টাস্কগুলির একটি ক্রম নেয়, সেগুলির সবগুলি চালায় এবং ইনপুটগুলির মতোই একই ক্রমে তাদের ফলাফলের একটি তালিকা ফিরিয়ে দেয়। এটি "এই সবগুলি চালান এবং আমাকে সব ফলাফল দিন" - এই সাধারণ ক্ষেত্রে একটি উচ্চ-স্তরের, সুবিধাজনক ফাংশন।
import asyncio
async def fetch_data(source, delay):
print(f"Fetching from {source}...")
await asyncio.sleep(delay)
return {"source": source, "data": f"some data from {source}"}
async def main():
# gather can take coroutines directly
results = await asyncio.gather(
fetch_data("API", 2),
fetch_data("Database", 3),
fetch_data("Cache", 1)
)
print(results)
asyncio.run(main())
`gather()` সহ ত্রুটি পরিচালনা: ডিফল্টরূপে, যদি `gather()`-এ পাস করা কোনো অপেক্ষাযোগ্য (awaitable) একটি ব্যতিক্রম উত্থাপন করে, তবে `gather()` অবিলম্বে সেই ব্যতিক্রমটি প্রচার করে, এবং অন্যান্য চলমান টাস্কগুলি বাতিল হয়ে যায়। আপনি `return_exceptions=True` ব্যবহার করে এই আচরণটি পরিবর্তন করতে পারেন। এই মোডে, একটি ব্যতিক্রম উত্থাপনের পরিবর্তে, এটি সংশ্লিষ্ট অবস্থানে ফলাফলের তালিকায় স্থাপন করা হবে।
# ... inside main()
results = await asyncio.gather(
fetch_data("API", 2),
asyncio.create_task(worker(1)), # This will raise a ValueError
fetch_data("Cache", 1),
return_exceptions=True
)
# results will contain a mix of successful results and exception objects
print(results)
সূক্ষ্ম-দানাযুক্ত নিয়ন্ত্রণ: `asyncio.wait()`
asyncio.wait()
একটি নিম্ন-স্তরের ফাংশন যা টাস্কগুলির একটি গ্রুপের উপর আরও বিস্তারিত নিয়ন্ত্রণ প্রদান করে। `gather()`-এর মতো নয়, এটি সরাসরি ফলাফল ফিরিয়ে দেয় না। পরিবর্তে, এটি টাস্কের দুটি সেট ফিরিয়ে দেয়: `done` এবং `pending`।
এর সবচেয়ে শক্তিশালী বৈশিষ্ট্য হল `return_when` প্যারামিটার, যা হতে পারে:
asyncio.ALL_COMPLETED
(ডিফল্ট): সমস্ত টাস্ক শেষ হলে ফিরে আসে।asyncio.FIRST_COMPLETED
: অন্তত একটি টাস্ক শেষ হওয়ার সাথে সাথেই ফিরে আসে।asyncio.FIRST_EXCEPTION
: যখন একটি টাস্ক একটি ব্যতিক্রম উত্থাপন করে তখন ফিরে আসে। যদি কোনো টাস্ক ব্যতিক্রম উত্থাপন না করে, তবে এটি `ALL_COMPLETED`-এর সমতুল্য।
একাধিক রিডান্ডেন্ট ডেটা সোর্স ক্যোয়ারী করা এবং যে প্রথম প্রতিক্রিয়া জানায় সেটি ব্যবহার করার মতো পরিস্থিতিতে এটি অত্যন্ত কার্যকর:
import asyncio
async def query_source(name, delay):
await asyncio.sleep(delay)
return f"Result from {name}"
async def main():
tasks = [
asyncio.create_task(query_source("Fast Mirror", 0.5)),
asyncio.create_task(query_source("Slow Main DB", 2.0)),
asyncio.create_task(query_source("Geographic Replica", 0.8))
]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
# Get the result from the completed task
first_result = done.pop().result()
print(f"Got first result: {first_result}")
# We now have pending tasks that are still running. It's crucial to clean them up!
print(f"Cancelling {len(pending)} pending tasks...")
for task in pending:
task.cancel()
# Await the cancelled tasks to allow them to process the cancellation
await asyncio.gather(*pending, return_exceptions=True)
print("Cleanup complete.")
asyncio.run(main())
TaskGroup বনাম gather() বনাম wait(): কখন কোনটি ব্যবহার করবেন?
- আপনার ডিফল্ট পছন্দ হিসাবে `asyncio.TaskGroup` (Python 3.11+) ব্যবহার করুন। এর স্ট্রাকচার্ড কনকারেন্সি মডেল একটি একক লজিক্যাল অপারেশনের অন্তর্গত টাস্কগুলির একটি গ্রুপ পরিচালনা করার জন্য নিরাপদ, পরিষ্কার এবং কম ত্রুটি-প্রবণ।
- যখন আপনার স্বাধীন টাস্কগুলির একটি গ্রুপ চালানোর প্রয়োজন হয় এবং কেবল তাদের ফলাফলের একটি তালিকা চান তখন `asyncio.gather()` ব্যবহার করুন। এটি এখনও খুব দরকারী এবং সাধারণ ক্ষেত্রে, বিশেষ করে 3.11-এর আগের পাইথন সংস্করণগুলিতে, এটি কিছুটা সংক্ষিপ্ত।
- উন্নত পরিস্থিতিগুলির জন্য `asyncio.wait()` ব্যবহার করুন যেখানে আপনার সমাপ্তির শর্তগুলির (যেমন, প্রথম ফলাফলের জন্য অপেক্ষা করা) উপর সূক্ষ্ম-দানাযুক্ত নিয়ন্ত্রণের প্রয়োজন এবং আপনি অবশিষ্ট পেন্ডিং টাস্কগুলি ম্যানুয়ালি পরিচালনা করতে প্রস্তুত।
টাস্কের জীবনচক্র এবং ব্যবস্থাপনা
একবার একটি টাস্ক তৈরি হয়ে গেলে, আপনি `Task` অবজেক্টের পদ্ধতিগুলি ব্যবহার করে এটির সাথে ইন্টারঅ্যাক্ট করতে পারেন।
টাস্কের অবস্থা পরীক্ষা করা
task.done()
: টাস্কটি সম্পূর্ণ হলে (হয় সফলভাবে, একটি ব্যতিক্রম সহ, অথবা বাতিলকরণের মাধ্যমে) `True` প্রদান করে।task.cancelled()
: টাস্কটি বাতিল হলে `True` প্রদান করে।task.exception()
: যদি টাস্কটি একটি ব্যতিক্রম উত্থাপন করে, তবে এটি ব্যতিক্রম অবজেক্টটি প্রদান করে। অন্যথায়, এটি `None` প্রদান করে। আপনি শুধুমাত্র টাস্কটি `done()` হওয়ার পরেই এটি কল করতে পারেন।
ফলাফল পুনরুদ্ধার
একটি টাস্কের ফলাফল পাওয়ার প্রধান উপায় হল কেবল `await task` ব্যবহার করা। যদি টাস্কটি সফলভাবে শেষ হয়, তবে এটি মানটি প্রদান করে। যদি এটি একটি ব্যতিক্রম উত্থাপন করে, `await task` সেই ব্যতিক্রমটি পুনরায় উত্থাপন করবে। যদি এটি বাতিল করা হয়, `await task` একটি `CancelledError` উত্থাপন করবে।
বিকল্পভাবে, যদি আপনি জানেন যে একটি টাস্ক `done()` হয়েছে, আপনি `task.result()` কল করতে পারেন। এটি মান ফেরত দেওয়া বা ব্যতিক্রম উত্থাপনের ক্ষেত্রে `await task`-এর মতোই আচরণ করে।
বাতিলকরণের শিল্প
দীর্ঘ-চলমান অপারেশনগুলি সুন্দরভাবে বাতিল করতে পারা দৃঢ় অ্যাপ্লিকেশন তৈরির জন্য গুরুত্বপূর্ণ। একটি টাইমআউট, একটি ব্যবহারকারীর অনুরোধ, বা সিস্টেমের অন্য কোথাও একটি ত্রুটির কারণে আপনার একটি টাস্ক বাতিল করার প্রয়োজন হতে পারে।
আপনি task.cancel()
পদ্ধতিটি কল করে একটি টাস্ক বাতিল করেন। তবে, এটি অবিলম্বে টাস্কটিকে বন্ধ করে না। পরিবর্তে, এটি পরবর্তী await
পয়েন্টে কো-রুটিনের ভিতরে একটি `CancelledError` ব্যতিক্রম নিক্ষেপ করার জন্য শিডিউল করে। এটি একটি গুরুত্বপূর্ণ বিস্তারিত। এটি কো-রুটিনকে এক্সিট করার আগে পরিষ্কার করার সুযোগ দেয়।
একটি সু-আচরণযুক্ত কো-রুটিনকে এই `CancelledError` সুন্দরভাবে পরিচালনা করা উচিত, সাধারণত একটি `try...finally` ব্লক ব্যবহার করে যাতে ফাইল হ্যান্ডেল বা ডাটাবেস সংযোগের মতো সংস্থানগুলি বন্ধ করা নিশ্চিত হয়।
import asyncio
async def resource_intensive_task():
print("Acquiring resource (e.g., opening a connection)...")
try:
for i in range(10):
print(f"Working... step {i+1}")
await asyncio.sleep(1) # This is an await point where CancelledError can be injected
except asyncio.CancelledError:
print("Task was cancelled! Cleaning up...")
raise # It's good practice to re-raise CancelledError
finally:
print("Releasing resource (e.g., closing connection). This always runs.")
async def main():
task = asyncio.create_task(resource_intensive_task())
# Let it run for a bit
await asyncio.sleep(2.5)
print("Main decides to cancel the task.")
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Main has confirmed the task was cancelled.")
asyncio.run(main())
`finally` ব্লকটি এক্সিকিউট হওয়ার গ্যারান্টিযুক্ত, যা এটিকে পরিষ্কার করার যুক্তির জন্য নিখুঁত জায়গা করে তোলে।
`asyncio.timeout()` এবং `asyncio.wait_for()` দিয়ে টাইমআউট যোগ করা
ম্যানুয়ালি স্লিপ করা এবং বাতিল করা ক্লান্তিকর। `asyncio` এই সাধারণ প্যাটার্নের জন্য সাহায্যকারী প্রদান করে।
Python 3.11+-এ, `asyncio.timeout()` কনটেক্সট ম্যানেজারটি পছন্দসই উপায়:
async def long_running_operation():
await asyncio.sleep(10)
print("Operation finished")
async def main():
try:
async with asyncio.timeout(2): # Set a 2-second timeout
await long_running_operation()
except TimeoutError:
print("The operation timed out!")
asyncio.run(main())
পুরানো পাইথন সংস্করণগুলির জন্য, আপনি `asyncio.wait_for()` ব্যবহার করতে পারেন। এটি একইভাবে কাজ করে তবে অপেক্ষাযোগ্যটিকে একটি ফাংশন কলে মোড়কে রাখে:
async def main_legacy():
try:
await asyncio.wait_for(long_running_operation(), timeout=2)
except asyncio.TimeoutError:
print("The operation timed out!")
asyncio.run(main_legacy())
উভয় টুলই টাইমআউট পৌঁছালে ভিতরের টাস্ক বাতিল করে কাজ করে, একটি `TimeoutError` উত্থাপন করে (যা `CancelledError`-এর একটি সাবক্লাস)।
সাধারণ সমস্যা এবং সেরা অনুশীলন
টাস্ক নিয়ে কাজ করা শক্তিশালী, তবে কিছু সাধারণ ফাঁদ এড়ানো উচিত।
- সমস্যা: "ফায়ার অ্যান্ড ফরগেট" ভুল। `create_task` দিয়ে একটি টাস্ক তৈরি করা এবং তারপর এটিকে (বা `TaskGroup`-এর মতো একটি ম্যানেজারকে) কখনও অপেক্ষা না করা বিপজ্জনক। যদি সেই টাস্কটি একটি ব্যতিক্রম উত্থাপন করে, তবে ব্যতিক্রমটি নীরবে হারিয়ে যেতে পারে, এবং আপনার প্রোগ্রামটি টাস্কটি তার কাজ শেষ করার আগেই এক্সিট হতে পারে। প্রতিটি টাস্কের জন্য সর্বদা একটি পরিষ্কার মালিক রাখুন যিনি এর ফলাফলের জন্য অপেক্ষা করার জন্য দায়ী।
- সমস্যা: `asyncio.run()` এবং `create_task()` গুলিয়ে ফেলা। `asyncio.run(my_coro())` হল একটি `asyncio` প্রোগ্রাম শুরু করার প্রধান এন্ট্রি পয়েন্ট। এটি একটি নতুন ইভেন্ট লুপ তৈরি করে এবং প্রদত্ত কো-রুটিনটি সম্পূর্ণ না হওয়া পর্যন্ত চালায়। `asyncio.create_task(my_coro())` একটি ইতিমধ্যে চলমান অ্যাসিঙ্ক ফাংশনের ভিতরে কনকারেন্ট এক্সিকিউশন শিডিউল করার জন্য ব্যবহৃত হয়।
- সেরা অনুশীলন: আধুনিক পাইথনের জন্য `TaskGroup` ব্যবহার করুন। এর ডিজাইন ভুলে যাওয়া টাস্ক এবং অ-হ্যান্ডেল করা ব্যতিক্রমগুলির মতো অনেক সাধারণ ত্রুটি প্রতিরোধ করে। আপনি যদি Python 3.11 বা তার পরের সংস্করণ ব্যবহার করেন, তবে এটিকে আপনার ডিফল্ট পছন্দ হিসাবে ব্যবহার করুন।
- সেরা অনুশীলন: আপনার টাস্কগুলির নাম দিন। একটি টাস্ক তৈরি করার সময়, `name` প্যারামিটার ব্যবহার করুন: `asyncio.create_task(my_coro(), name='DataProcessor-123')`। ডিবাগিংয়ের জন্য এটি অমূল্য। যখন আপনি সমস্ত চলমান টাস্ক তালিকাভুক্ত করেন, তখন অর্থপূর্ণ নাম থাকা আপনার প্রোগ্রাম কী করছে তা বুঝতে সাহায্য করে।
- সেরা অনুশীলন: সুচারু শাটডাউন নিশ্চিত করুন। যখন আপনার অ্যাপ্লিকেশনটি শাটডাউন করার প্রয়োজন হয়, তখন নিশ্চিত করুন যে আপনার কাছে সমস্ত চলমান ব্যাকগ্রাউন্ড টাস্ক বাতিল করার এবং সেগুলি সঠিকভাবে পরিষ্কার না হওয়া পর্যন্ত অপেক্ষা করার একটি প্রক্রিয়া রয়েছে।
উন্নত ধারণা: একটি ক্ষুদ্র দৃষ্টিপাত
ডিবাগিং এবং ইন্ট্রোস্পেকশনের জন্য, `asyncio` কয়েকটি দরকারী ফাংশন সরবরাহ করে:
asyncio.current_task()
: বর্তমানে এক্সিকিউট হচ্ছে এমন কোডের জন্য `Task` অবজেক্ট প্রদান করে।asyncio.all_tasks()
: ইভেন্ট লুপ দ্বারা বর্তমানে পরিচালিত সমস্ত `Task` অবজেক্টের একটি সেট প্রদান করে। কী চলছে তা দেখার জন্য ডিবাগিংয়ের জন্য এটি দুর্দান্ত।
আপনি `task.add_done_callback()` ব্যবহার করে টাস্কগুলিতে সমাপ্তি কলব্যাক যুক্ত করতে পারেন। যদিও এটি কার্যকর হতে পারে, তবে এটি প্রায়শই একটি আরও জটিল, কলব্যাক-স্টাইলের কোড কাঠামো তৈরি করে। পঠনযোগ্যতা এবং রক্ষণাবেক্ষণের জন্য `await`, `TaskGroup`, বা `gather` ব্যবহার করে আধুনিক পদ্ধতিগুলি সাধারণত পছন্দ করা হয়।
উপসংহার
`asyncio.Task` আধুনিক পাইথনে কনকারেন্সির ইঞ্জিন। টাস্কগুলির জীবনচক্র কিভাবে তৈরি, পরিচালনা এবং সুন্দরভাবে হ্যান্ডেল করতে হয় তা বোঝার মাধ্যমে, আপনি আপনার I/O-বাউন্ড অ্যাপ্লিকেশনগুলিকে ধীর, সিকোয়েন্সিয়াল প্রক্রিয়া থেকে অত্যন্ত দক্ষ, স্কেলেবল এবং প্রতিক্রিয়াশীল সিস্টেমে রূপান্তর করতে পারেন।
আমরা `create_task()` দিয়ে একটি কো-রুটিন শিডিউল করার মৌলিক ধারণা থেকে শুরু করে `TaskGroup`, `gather()` এবং `wait()` দিয়ে জটিল ওয়ার্কফ্লো অর্কেস্ট্রেট করা পর্যন্ত আলোচনা করেছি। শক্তিশালী ত্রুটি পরিচালনা, বাতিলকরণ এবং টাইমআউটগুলির গুরুত্বপূর্ণ গুরুত্বও আমরা স্থিতিস্থাপক সফটওয়্যার তৈরির জন্য অন্বেষণ করেছি।
অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের জগত বিশাল, তবে টাস্কগুলিতে দক্ষতা অর্জন করা আপনার নেওয়া সবচেয়ে গুরুত্বপূর্ণ পদক্ষেপ। পরীক্ষা শুরু করুন। আপনার অ্যাপ্লিকেশনের একটি সিকোয়েন্সিয়াল, I/O-বাউন্ড অংশকে কনকারেন্ট টাস্ক ব্যবহার করতে রূপান্তর করুন এবং নিজেই পারফরম্যান্সের উন্নতি দেখুন। কনকারেন্সির শক্তিকে আলিঙ্গন করুন, এবং আপনি উচ্চ-পারফরম্যান্স পাইথন অ্যাপ্লিকেশনগুলির পরবর্তী প্রজন্ম তৈরি করার জন্য সুসজ্জিত হবেন।