কোটলিন কোরাউটিন আপনাকে পরিচ্ছন্ন ও সরলীকৃত অ্যাসিঙ্ক্রোনাস কোড লিখতে সক্ষম করে, যা নেটওয়ার্ক কল বা ডিস্ক অপারেশনের মতো দীর্ঘ সময় ধরে চলা কাজগুলো পরিচালনা করার পাশাপাশি আপনার অ্যাপকে রেসপন্সিভ রাখে।
এই টপিকে অ্যান্ড্রয়েডে কো-রুটিন সম্পর্কে বিস্তারিত আলোচনা করা হয়েছে। আপনি যদি কো-রুটিনের সাথে পরিচিত না হন, তবে এই টপিকটি পড়ার আগে অবশ্যই "অ্যান্ড্রয়েডে কোটলিন কো-রুটিন" পড়ে নেবেন।
দীর্ঘস্থায়ী কাজগুলো পরিচালনা করুন
দীর্ঘমেয়াদী কাজগুলি পরিচালনা করার জন্য দুটি অপারেশন যোগ করে কোরাউটিনগুলি সাধারণ ফাংশনের উপর ভিত্তি করে তৈরি হয় return invoke এবং call ছাড়াও, কোরাউটিনগুলি suspend এবং resume যোগ করে:
-
suspendবর্তমান কো-রুটিনের নির্বাহ থামিয়ে দেয় এবং সমস্ত লোকাল ভেরিয়েবল সংরক্ষণ করে। -
resumeএকটি স্থগিত কো-রুটিনের কার্য সম্পাদনকে সেই স্থান থেকেই পুনরায় শুরু করে, যেখান থেকে এটিকে স্থগিত করা হয়েছিল।
আপনি শুধুমাত্র অন্য suspend ফাংশন থেকে অথবা launch মতো কোনো কো-রুটিন বিল্ডার ব্যবহার করে একটি নতুন কো-রুটিন শুরু করার মাধ্যমে suspend ফাংশন কল করতে পারেন।
নিম্নলিখিত উদাহরণটি একটি কাল্পনিক দীর্ঘ-চলমান কাজের জন্য একটি সরল কো-রুটিন বাস্তবায়ন দেখায়:
suspend fun fetchDocs() { // Dispatchers.Main val result = get("https://developer.android.com") // Dispatchers.IO for `get` show(result) // Dispatchers.Main } suspend fun get(url: String) = withContext(Dispatchers.IO) { }
এই উদাহরণে, get() মেইন থ্রেডেই চলে, কিন্তু নেটওয়ার্ক রিকোয়েস্ট শুরু করার আগে এটি কো-রুটিনটিকে সাসপেন্ড করে। নেটওয়ার্ক রিকোয়েস্টটি সম্পন্ন হলে, get মেইন থ্রেডকে জানানোর জন্য কলব্যাক ব্যবহার না করে সাসপেন্ড করা কো-রুটিনটিকে পুনরায় চালু করে।
কোটলিন কোন ফাংশন চলছে এবং এর সাথে থাকা লোকাল ভেরিয়েবলগুলো পরিচালনা করার জন্য একটি স্ট্যাক ফ্রেম ব্যবহার করে। একটি কো-রুটিন সাসপেন্ড করার সময়, বর্তমান স্ট্যাক ফ্রেমটি কপি করে পরবর্তী ব্যবহারের জন্য সংরক্ষণ করা হয়। পুনরায় চালু করার সময়, স্ট্যাক ফ্রেমটি যেখানে সংরক্ষণ করা হয়েছিল সেখান থেকে আবার কপি করা হয় এবং ফাংশনটি পুনরায় চলতে শুরু করে। যদিও কোডটিকে একটি সাধারণ সিকোয়েনশিয়াল ব্লকিং রিকোয়েস্টের মতো মনে হতে পারে, কো-রুটিনটি নিশ্চিত করে যে নেটওয়ার্ক রিকোয়েস্টটি মেইন থ্রেডকে ব্লক না করে।
মেইন-সেফটির জন্য কো-রুটিন ব্যবহার করুন
কোটলিন কোরাউটিনগুলো কোন থ্রেডে চলবে তা নির্ধারণ করতে ডিসপ্যাচার ব্যবহার করে। মেইন থ্রেডের বাইরে কোড চালানোর জন্য, আপনি কোটলিন কোরাউটিনগুলোকে ডিফল্ট অথবা আইও (IO) ডিসপ্যাচারে কাজ করার নির্দেশ দিতে পারেন। কোটলিনে, সমস্ত কোরাউটিনকে অবশ্যই একটি ডিসপ্যাচারের মধ্যে চলতে হয়, এমনকি যখন সেগুলো মেইন থ্রেডে চলে তখনও। কোরাউটিনগুলো নিজেদেরকে সাসপেন্ড করতে পারে এবং সেগুলোকে পুনরায় চালু করার দায়িত্ব ডিসপ্যাচারের।
কো-রুটিনগুলো কোথায় রান করবে তা নির্দিষ্ট করার জন্য, কোটলিন তিনটি ডিসপ্যাচার প্রদান করে যা আপনি ব্যবহার করতে পারেন:
- Dispatchers.Main - অ্যান্ড্রয়েডের প্রধান থ্রেডে একটি কো-রুটিন চালানোর জন্য এই ডিসপ্যাচারটি ব্যবহার করুন। এটি শুধুমাত্র UI-এর সাথে ইন্টারঅ্যাক্ট করতে এবং দ্রুত কাজ সম্পাদনের জন্য ব্যবহার করা উচিত। উদাহরণস্বরূপ,
suspendফাংশন কল করা, অ্যান্ড্রয়েড UI ফ্রেমওয়ার্ক অপারেশন চালানো এবংLiveDataঅবজেক্ট আপডেট করা। - Dispatchers.IO - এই ডিসপ্যাচারটি মূল থ্রেডের বাইরে ডিস্ক বা নেটওয়ার্ক I/O সম্পাদনের জন্য অপ্টিমাইজ করা হয়েছে। এর উদাহরণগুলির মধ্যে রয়েছে Room কম্পোনেন্ট ব্যবহার করা, ফাইল থেকে পড়া বা ফাইলে লেখা এবং যেকোনো নেটওয়ার্ক অপারেশন চালানো।
- Dispatchers.Default - এই ডিসপ্যাচারটি মেইন থ্রেডের বাইরে সিপিইউ-নিবিড় কাজ সম্পাদন করার জন্য অপ্টিমাইজ করা হয়েছে। এর ব্যবহারের উদাহরণগুলির মধ্যে রয়েছে একটি তালিকা সাজানো এবং JSON পার্স করা।
পূর্ববর্তী উদাহরণটি অনুসরণ করে, আপনি ডিসপ্যাচার ব্যবহার করে get ফাংশনটিকে পুনরায় সংজ্ঞায়িত করতে পারেন। get এর বডির ভিতরে, withContext(Dispatchers.IO) কল করে একটি ব্লক তৈরি করুন যা IO থ্রেড পুলে চলে। ঐ ব্লকের ভিতরে রাখা যেকোনো কোড সর্বদা IO ডিসপ্যাচারের মাধ্যমে এক্সিকিউট হয়। যেহেতু withContext নিজেই একটি সাসপেন্ড ফাংশন, তাই get ফাংশনটিও একটি সাসপেন্ড ফাংশন।
suspend fun fetchDocs() { // Dispatchers.Main val result = get("developer.android.com") // Dispatchers.Main show(result) // Dispatchers.Main } suspend fun get(url: String) = // Dispatchers.Main withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block) /* perform network IO here */ // Dispatchers.IO (main-safety block) } // Dispatchers.Main
কো-রুটিনের সাহায্যে আপনি অত্যন্ত সূক্ষ্মভাবে থ্রেড ডিসপ্যাচ করতে পারেন। যেহেতু withContext() আপনাকে কোনো কলব্যাক ব্যবহার না করেই কোডের যেকোনো লাইনের থ্রেড পুল নিয়ন্ত্রণ করতে দেয়, তাই আপনি এটিকে ডাটাবেস থেকে ডেটা পড়া বা নেটওয়ার্ক রিকোয়েস্ট করার মতো খুব ছোট ফাংশনেও প্রয়োগ করতে পারেন। একটি ভালো অভ্যাস হলো withContext() ব্যবহার করে প্রতিটি ফাংশনকে মেইন-সেফ করা, যার অর্থ হলো আপনি মেইন থ্রেড থেকে ফাংশনটি কল করতে পারবেন। এইভাবে, ফাংশনটি চালানোর জন্য কোন থ্রেড ব্যবহার করা উচিত, তা নিয়ে কলারকে কখনও ভাবতে হয় না।
পূর্ববর্তী উদাহরণে, fetchDocs() মেইন থ্রেডে এক্সিকিউট হয়; তবে, এটি নিরাপদে get কল করতে পারে, যা ব্যাকগ্রাউন্ডে একটি নেটওয়ার্ক রিকোয়েস্ট সম্পাদন করে। যেহেতু কো-রুটিন suspend এবং resume সমর্থন করে, তাই withContext ব্লকটির কাজ শেষ হওয়ার সাথে সাথেই মেইন থ্রেডের কো-রুটিনটি get ফলাফলসহ পুনরায় চালু হয়।
withContext() এর কর্মক্ষমতা
একটি সমতুল্য কলব্যাক-ভিত্তিক ইমপ্লিমেন্টেশনের তুলনায় withContext() কোনো অতিরিক্ত ওভারহেড যোগ করে না। অধিকন্তু, কিছু পরিস্থিতিতে একটি সমতুল্য কলব্যাক-ভিত্তিক ইমপ্লিমেন্টেশনের চেয়েও withContext() কলগুলোকে অপ্টিমাইজ করা সম্ভব। উদাহরণস্বরূপ, যদি একটি ফাংশন কোনো নেটওয়ার্কে দশবার কল করে, তবে আপনি একটি বাইরের withContext() ব্যবহার করে Kotlin-কে শুধুমাত্র একবার থ্রেড পরিবর্তন করতে বলতে পারেন। সেক্ষেত্রে, নেটওয়ার্ক লাইব্রেরিটি একাধিকবার withContext() ব্যবহার করলেও, এটি একই ডিসপ্যাচারে থাকে এবং থ্রেড পরিবর্তন এড়িয়ে চলে। এছাড়াও, যখনই সম্ভব থ্রেড পরিবর্তন এড়াতে Kotlin, Dispatchers.Default এবং Dispatchers.IO মধ্যে পরিবর্তনকে অপ্টিমাইজ করে।
একটি কো-রুটিন শুরু করুন
আপনি দুটি উপায়ের যেকোনো একটিতে কো-রুটিন শুরু করতে পারেন:
-
launchএকটি নতুন কো-রুটিন শুরু করে এবং কলকারীকে ফলাফল ফেরত দেয় না। "ফায়ার অ্যান্ড ফরগেট" ধরনের যেকোনো কাজlaunchব্যবহার করে শুরু করা যায়। -
asyncএকটি নতুন কো-রুটিন শুরু করে এবংawaitনামক একটি সাসপেন্ড ফাংশনের মাধ্যমে ফলাফল রিটার্ন করার সুযোগ দেয়।
সাধারণত, একটি সাধারণ ফাংশন থেকে নতুন কো-রুটিন launch উচিত, কারণ একটি সাধারণ ফাংশন await কল করতে পারে না। শুধুমাত্র অন্য কোনো কো-রুটিনের ভেতরে অথবা কোনো 'suspend' ফাংশনের ভেতরে প্যারালাল ডিকম্পোজিশন করার সময় async ব্যবহার করুন।
সমান্তরাল পচন
একটি suspend ফাংশনের ভিতরে শুরু হওয়া সমস্ত কো-রুটিন অবশ্যই সেই ফাংশনটি রিটার্ন করার সাথে সাথে বন্ধ হয়ে যেতে হবে, তাই আপনাকে সম্ভবত নিশ্চিত করতে হবে যে রিটার্ন করার আগে সেই কো-রুটিনগুলো শেষ হয়। কোটলিনে স্ট্রাকচার্ড কনকারেন্সির মাধ্যমে, আপনি একটি coroutineScope সংজ্ঞায়িত করতে পারেন যা এক বা একাধিক কো-রুটিন শুরু করে। তারপর, await() (একটি কো-রুটিনের জন্য) বা awaitAll() (একাধিক কো-রুটিনের জন্য) ব্যবহার করে, আপনি নিশ্চিত করতে পারেন যে ফাংশন থেকে রিটার্ন করার আগে এই কো-রুটিনগুলো শেষ হবে।
উদাহরণস্বরূপ, ধরা যাক একটি coroutineScope আছে যা অ্যাসিঙ্ক্রোনাসভাবে দুটি ডকুমেন্ট ফেচ করে। প্রতিটি ডিফার্ড রেফারেন্সে await() কল করার মাধ্যমে আমরা নিশ্চিত করি যে একটি ভ্যালু রিটার্ন করার আগে উভয় async অপারেশনই সম্পন্ন হবে:
suspend fun fetchTwoDocs() = coroutineScope { val deferredOne = async { fetchDoc(1) } val deferredTwo = async { fetchDoc(2) } deferredOne.await() deferredTwo.await() }
আপনি কালেকশনের উপরেও awaitAll() ব্যবহার করতে পারেন, যেমনটি নিচের উদাহরণে দেখানো হয়েছে:
suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main) coroutineScope { val deferreds = listOf( // fetch two docs at the same time async { fetchDoc(1) }, // async returns a result for the first doc async { fetchDoc(2) } // async returns a result for the second doc ) deferreds.awaitAll() // use awaitAll to wait for both network requests }
যদিও fetchTwoDocs() ফাংশনটি async ব্যবহার করে নতুন কো-রুটিন চালু করে, তবুও রিটার্ন করার আগে চালু হওয়া কো-রুটিনগুলো শেষ হওয়ার জন্য অপেক্ষা করতে ফাংশনটি awaitAll() ব্যবহার করে। তবে মনে রাখবেন, আমরা awaitAll() কল না করলেও, সমস্ত নতুন কো-রুটিন সম্পূর্ণ না হওয়া পর্যন্ত coroutineScope বিল্ডারটি fetchTwoDocs কল করা কো-রুটিনটিকে পুনরায় চালু করে না।
এছাড়াও, coroutineScope কো-রুটিনগুলো দ্বারা নিক্ষিপ্ত যেকোনো ব্যতিক্রম (exception) ধরে ফেলে এবং সেগুলোকে কলারের কাছে ফেরত পাঠায়।
সমান্তরাল ডিকম্পোজিশন সম্পর্কে আরও তথ্যের জন্য, সাসপেন্ডিং ফাংশন কম্পোজ করা দেখুন।
কোরাউটিন ধারণা
CoroutineScope
একটি CoroutineScope launch বা async ব্যবহার করে এর দ্বারা তৈরি যেকোনো কোরাউটিনের হিসাব রাখে। চলমান কাজটি (অর্থাৎ, চালু কোরাউটিনগুলো) যেকোনো সময়ে scope.cancel() কল করে বাতিল করা যায়। অ্যান্ড্রয়েডে, কিছু KTX লাইব্রেরি নির্দিষ্ট লাইফসাইকেল ক্লাসের জন্য তাদের নিজস্ব CoroutineScope প্রদান করে। উদাহরণস্বরূপ, ViewModel একটি viewModelScope এবং Lifecycle এর একটি lifecycleScope রয়েছে। তবে, একটি ডিসপ্যাচারের মতো নয়, একটি CoroutineScope কোরাউটিনগুলোকে রান করে না।
"Background threading on Android with Coroutines"- এর উদাহরণগুলিতেও viewModelScope ব্যবহৃত হয়। তবে, আপনার অ্যাপের কোনো নির্দিষ্ট লেয়ারে কোরাউটাইনের লাইফসাইকেল নিয়ন্ত্রণ করার জন্য যদি আপনার নিজস্ব CoroutineScope তৈরি করার প্রয়োজন হয়, তাহলে আপনি নিম্নলিখিত উপায়ে তা তৈরি করতে পারেন:
class ExampleClass { // Job and Dispatcher are combined into a CoroutineContext which // will be discussed shortly val scope = CoroutineScope(Job() + Dispatchers.Main) fun exampleMethod() { // Starts a new coroutine within the scope scope.launch { // New coroutine that can call suspend functions fetchDocs() } } fun cleanUp() { // Cancel the scope to cancel ongoing coroutines work scope.cancel() } }
বাতিল করা স্কোপ আর কোনো কো-রুটিন তৈরি করতে পারে না। তাই, শুধুমাত্র যখন এর লাইফসাইকেল নিয়ন্ত্রণকারী ক্লাসটি ধ্বংস করা হচ্ছে, তখনই আপনার scope.cancel() কল করা উচিত। viewModelScope ব্যবহার করার সময়, ViewModel ক্লাসটি তার onCleared() মেথডে আপনার জন্য স্কোপটি স্বয়ংক্রিয়ভাবে বাতিল করে দেয়।
চাকরি
একটি Job হলো একটি কোরাউটিনের হ্যান্ডেল। launch বা async ব্যবহার করে আপনি যে কোরাউটিন তৈরি করেন, তার প্রতিটি একটি Job ইনস্ট্যান্স রিটার্ন করে, যা কোরাউটিনটিকে অনন্যভাবে শনাক্ত করে এবং এর লাইফসাইকেল পরিচালনা করে। এর লাইফসাইকেল আরও ভালোভাবে পরিচালনা করার জন্য আপনি একটি Job কে CoroutineScope এও পাস করতে পারেন, যেমনটি নিচের উদাহরণে দেখানো হয়েছে:
class ExampleClass { // ... fun exampleMethod() { // Handle to the coroutine, you can control its lifecycle val job = scope.launch { // New coroutine } if (condition) { // Cancel the coroutine started above, this doesn't affect the scope // this coroutine was launched in job.cancel() } } }
CoroutineContext
একটি CoroutineContext নিম্নলিখিত উপাদানগুলির সেট ব্যবহার করে একটি কো-রুটিনের আচরণ নির্ধারণ করে:
-
Job: কো-রুটিনের জীবনচক্র নিয়ন্ত্রণ করে। -
CoroutineDispatcher: উপযুক্ত থ্রেডে কাজ প্রেরণ করে। -
CoroutineName: কো-রুটিনের নাম, যা ডিবাগিংয়ের জন্য উপযোগী। -
CoroutineExceptionHandler: ধরা না পড়া এক্সেপশনগুলো পরিচালনা করে।
একটি স্কোপের মধ্যে তৈরি নতুন কোরাউটিনের জন্য, নতুন কোরাউটিনটিতে একটি নতুন Job ইনস্ট্যান্স বরাদ্দ করা হয় এবং অন্যান্য CoroutineContext এলিমেন্টগুলো ধারণকারী স্কোপ থেকে উত্তরাধিকারসূত্রে প্রাপ্ত হয়। আপনি launch বা async ফাংশনে একটি নতুন CoroutineContext পাস করে উত্তরাধিকারসূত্রে প্রাপ্ত এলিমেন্টগুলোকে ওভাররাইড করতে পারেন। মনে রাখবেন যে launch বা async এ একটি Job পাস করার কোনো প্রভাব নেই, কারণ একটি নতুন কোরাউটিনকে সর্বদা Job এর একটি নতুন ইনস্ট্যান্স বরাদ্দ করা হয়।
class ExampleClass { val scope = CoroutineScope(Job() + Dispatchers.Main) fun exampleMethod() { // Starts a new coroutine on Dispatchers.Main as it's the scope's default val job1 = scope.launch { // New coroutine with CoroutineName = "coroutine" (default) } // Starts a new coroutine on Dispatchers.Default val job2 = scope.launch(Dispatchers.Default + CoroutineName("BackgroundCoroutine")) { // New coroutine with CoroutineName = "BackgroundCoroutine" (overridden) } } }
অতিরিক্ত কোরাউটিন রিসোর্স
কো-রুটিন সম্পর্কিত আরও তথ্যের জন্য, নিম্নলিখিত লিঙ্কগুলি দেখুন: