Kotlin coroutines দিয়ে অ্যাপের পারফরম্যান্স উন্নত করুন

Kotlin coroutines আপনাকে পরিষ্কার, সরলীকৃত অ্যাসিঙ্ক্রোনাস কোড লিখতে সক্ষম করে যা নেটওয়ার্ক কল বা ডিস্ক অপারেশনের মতো দীর্ঘমেয়াদী কাজগুলি পরিচালনা করার সময় আপনার অ্যাপকে প্রতিক্রিয়াশীল রাখে।

এই বিষয়টি Android-এ coroutines এর বিস্তারিত বিবরণ প্রদান করে। আপনি যদি coroutines এর সাথে অপরিচিত হন, তাহলে এই বিষয় পড়ার আগে Android-এ Kotlin coroutines পড়তে ভুলবেন না।

দীর্ঘস্থায়ী কাজগুলি পরিচালনা করুন

Coroutines দীর্ঘ-চলমান কাজগুলি পরিচালনা করার জন্য দুটি অপারেশন যোগ করে নিয়মিত ফাংশন তৈরি করে। invoke (বা call ) এবং return পাশাপাশি, কোরোটিনগুলি suspend এবং resume যোগ করে:

  • suspend করা সমস্ত স্থানীয় ভেরিয়েবল সংরক্ষণ করে বর্তমান coroutine-এর কার্য সম্পাদনে বিরতি দেয়।
  • resume যেখানে এটি স্থগিত করা হয়েছিল সেখান থেকে একটি স্থগিত coroutine কার্যকর করা অব্যাহত থাকে।

আপনি শুধুমাত্র অন্যান্য suspend ফাংশন থেকে suspend ফাংশনকে কল করতে পারেন অথবা একটি নতুন কোরোটিন শুরু করার জন্য launch মতো একটি করটিন বিল্ডার ব্যবহার করে।

নিম্নোক্ত উদাহরণটি একটি অনুমানমূলক দীর্ঘ-চলমান কাজের জন্য একটি সাধারণ কোরোটিন বাস্তবায়ন দেখায়:

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() এখনও মূল থ্রেডে চলে, কিন্তু এটি নেটওয়ার্ক অনুরোধ শুরু করার আগে coroutine স্থগিত করে। নেটওয়ার্ক অনুরোধ সম্পূর্ণ হলে, মূল থ্রেডকে অবহিত করার জন্য একটি কলব্যাক ব্যবহার করার পরিবর্তে স্থগিত কোরোটিন পুনরায় শুরু get

কোন স্থানীয় ভেরিয়েবলের সাথে কোন ফাংশন চলছে তা পরিচালনা করতে Kotlin একটি স্ট্যাক ফ্রেম ব্যবহার করে। একটি করোটিন স্থগিত করার সময়, বর্তমান স্ট্যাক ফ্রেমটি অনুলিপি করা হয় এবং পরবর্তীতে সংরক্ষণ করা হয়। পুনরায় শুরু করার সময়, স্ট্যাক ফ্রেমটি যেখান থেকে সংরক্ষিত হয়েছিল সেখান থেকে আবার কপি করা হয় এবং ফাংশনটি আবার চলতে শুরু করে। যদিও কোডটি একটি সাধারণ ক্রমিক ব্লকিং অনুরোধের মতো দেখাতে পারে, কোরোটিন নিশ্চিত করে যে নেটওয়ার্ক অনুরোধটি মূল থ্রেডটিকে ব্লক করা এড়ায়।

প্রধান নিরাপত্তার জন্য coroutines ব্যবহার করুন

কোটলিন করোটিনগুলি প্রেরকদের ব্যবহার করে তা নির্ধারণ করতে কোন থ্রেডগুলি করোটিন সম্পাদনের জন্য ব্যবহৃত হয়। মূল থ্রেডের বাইরে কোড চালানোর জন্য, আপনি Kotlin coroutines কে বলতে পারেন ডিফল্ট বা IO ডিসপ্যাচারে কাজ করতে। কোটলিনে, সমস্ত কোরোটিন অবশ্যই একটি প্রেরণকারীতে চালাতে হবে, এমনকি যখন তারা মূল থ্রেডে চলছে। Coroutines নিজেদের স্থগিত করতে পারে, এবং প্রেরণকারী তাদের পুনরায় শুরু করার জন্য দায়ী।

কোরোটিনগুলি কোথায় চালানো উচিত তা নির্দিষ্ট করতে, কোটলিন তিনটি প্রেরণকারী সরবরাহ করে যা আপনি ব্যবহার করতে পারেন:

  • Dispatchers.Main - প্রধান Android থ্রেডে একটি coroutine চালানোর জন্য এই প্রেরণকারীটি ব্যবহার করুন৷ এটি শুধুমাত্র UI এর সাথে ইন্টারঅ্যাক্ট করার জন্য এবং দ্রুত কাজ করার জন্য ব্যবহার করা উচিত। উদাহরণগুলির মধ্যে রয়েছে কল suspend ফাংশন, অ্যান্ড্রয়েড UI ফ্রেমওয়ার্ক অপারেশন চালানো এবং LiveData অবজেক্ট আপডেট করা।
  • Dispatchers.IO - এই প্রেরণকারীটি প্রধান থ্রেডের বাইরে ডিস্ক বা নেটওয়ার্ক I/O সম্পাদন করার জন্য অপ্টিমাইজ করা হয়েছে। উদাহরণগুলির মধ্যে রয়েছে রুম কম্পোনেন্ট ব্যবহার করা, ফাইল থেকে পড়া বা লেখা, এবং যেকোনো নেটওয়ার্ক অপারেশন চালানো।
  • Dispatchers.Default - এই প্রেরণকারীকে প্রধান থ্রেডের বাইরে CPU-নিবিড় কাজ করার জন্য অপ্টিমাইজ করা হয়েছে। উদাহরণ ব্যবহারের ক্ষেত্রে একটি তালিকা বাছাই করা এবং JSON পার্স করা অন্তর্ভুক্ত।

পূর্ববর্তী উদাহরণটি অব্যাহত রেখে, আপনি get ফাংশন পুনরায় সংজ্ঞায়িত করতে প্রেরকদের ব্যবহার করতে পারেন। get এর বডির ভিতরে, IO থ্রেড পুলে চলে এমন একটি ব্লক তৈরি করতে withContext(Dispatchers.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
}

coroutines সঙ্গে, আপনি সূক্ষ্ম-দানা নিয়ন্ত্রণ সঙ্গে থ্রেড পাঠাতে পারেন. যেহেতু withContext() আপনাকে কলব্যাক প্রবর্তন না করেই কোডের যেকোনো লাইনের থ্রেড পুল নিয়ন্ত্রণ করতে দেয়, আপনি এটিকে খুব ছোট ফাংশনে প্রয়োগ করতে পারেন যেমন একটি ডাটাবেস থেকে পড়া বা একটি নেটওয়ার্ক অনুরোধ সম্পাদন করা। প্রতিটি ফাংশন main-safe কিনা তা নিশ্চিত করার জন্য withContext() ব্যবহার করা একটি ভাল অভ্যাস, যার মানে হল আপনি প্রধান থ্রেড থেকে ফাংশনটি কল করতে পারেন। এইভাবে, কলারকে কখনই ভাবতে হবে না যে ফাংশনটি কার্যকর করতে কোন থ্রেড ব্যবহার করা উচিত।

পূর্ববর্তী উদাহরণে, fetchDocs() প্রধান থ্রেডে কার্যকর করে; যাইহোক, এটি নিরাপদে get কল করতে পারে, যা পটভূমিতে একটি নেটওয়ার্ক অনুরোধ সম্পাদন করে। যেহেতু coroutines suspend এবং resume সমর্থন করে, তাই withContext ব্লকটি সম্পন্ন হওয়ার সাথে সাথেই মূল থ্রেডে coroutine পুনরায় শুরু get হয়।

withContext() এর কর্মক্ষমতা

withContext() সমতুল্য কলব্যাক-ভিত্তিক বাস্তবায়নের তুলনায় অতিরিক্ত ওভারহেড যোগ করে না। তদ্ব্যতীত, কিছু পরিস্থিতিতে সমতুল্য কলব্যাক-ভিত্তিক বাস্তবায়নের বাইরে withContext() অপ্টিমাইজ করা সম্ভব। উদাহরণস্বরূপ, যদি একটি ফাংশন একটি নেটওয়ার্কে দশটি কল করে, আপনি Kotlin কে বলতে পারেন একটি outer withContext() ব্যবহার করে শুধুমাত্র একবার থ্রেডগুলি পরিবর্তন করতে। তারপরে, যদিও নেটওয়ার্ক লাইব্রেরি withContext() একাধিকবার ব্যবহার করে, এটি একই প্রেরণকারীতে থাকে এবং থ্রেড পরিবর্তন করা এড়িয়ে যায়। উপরন্তু, Kotlin যখনই সম্ভব থ্রেড সুইচ এড়াতে Dispatchers.Default এবং Dispatchers.IO এর মধ্যে সুইচিং অপ্টিমাইজ করে।

একটি করুটিন শুরু করুন

আপনি দুটি উপায়ের একটিতে কোরোটিন শুরু করতে পারেন:

  • launch একটি নতুন কোরোটিন শুরু করে এবং কলকারীকে ফলাফল ফেরত দেয় না। "ফায়ার অ্যান্ড বিফোর" হিসাবে বিবেচিত যে কোনও কাজ launch ব্যবহার করে শুরু করা যেতে পারে।
  • async একটি নতুন coroutine শুরু করে এবং আপনাকে await নামক একটি সাসপেন্ড ফাংশন সহ ফলাফল ফেরত দিতে দেয়।

সাধারণত, আপনার একটি নিয়মিত ফাংশন থেকে একটি নতুন কোরোটিন launch উচিত, কারণ একটি নিয়মিত ফাংশন await করতে পারে না। async ব্যবহার করুন শুধুমাত্র যখন অন্য কোরোটিনের ভিতরে বা যখন একটি সাসপেন্ড ফাংশনের ভিতরে এবং সমান্তরাল পচন সম্পাদন করে।

সমান্তরাল পচন

একটি suspend ফাংশনের ভিতরে শুরু হওয়া সমস্ত কোরোটিনগুলি যখন সেই ফাংশনটি ফিরে আসে তখন অবশ্যই বন্ধ করতে হবে, তাই আপনাকে অবশ্যই গ্যারান্টি দিতে হবে যে সেই কোরোটিনগুলি ফিরে আসার আগে শেষ হবে৷ Kotlin-এ স্ট্রাকচার্ড কনকারেন্সি দিয়ে, আপনি একটি coroutineScope সংজ্ঞায়িত করতে পারেন যা এক বা একাধিক coroutine শুরু করে। তারপর, await() (একটি coroutine এর জন্য) বা awaitAll() (একাধিক কোরোটিনের জন্য) ব্যবহার করে, আপনি গ্যারান্টি দিতে পারেন যে ফাংশন থেকে ফিরে আসার আগে এই coroutines শেষ হয়ে যাবে।

একটি উদাহরণ হিসাবে, আসুন একটি 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 এর সাথে নতুন coroutines লঞ্চ করে, ফাংশনটি awaitAll() ব্যবহার করে সেই লঞ্চ করা কোরোটিনগুলি ফিরে আসার আগে শেষ হওয়ার জন্য অপেক্ষা করে। মনে রাখবেন যে, যদিও আমরা awaitAll() কে কল না করে থাকি, coroutineScope বিল্ডার সমস্ত নতুন coroutines সম্পূর্ণ না হওয়া পর্যন্ত fetchTwoDocs নামক coroutine পুনরায় শুরু করে না।

উপরন্তু, coroutineScope যেকোন ব্যতিক্রম ক্যাচ করে যেগুলি coroutines ছুড়ে দেয় এবং সেগুলিকে কলারের কাছে ফিরিয়ে দেয়।

সমান্তরাল পচন সম্পর্কে আরও তথ্যের জন্য, সাসপেন্ডিং ফাংশন রচনা করা দেখুন।

Coroutines ধারণা

CoroutineScope

একটি CoroutineScope launch বা async ব্যবহার করে তৈরি করা যেকোনো কোরোটিনের ট্র্যাক রাখে। চলমান কাজ (অর্থাৎ চলমান coroutines) যেকোনো সময়ে scope.cancel() কল করে বাতিল করা যেতে পারে। অ্যান্ড্রয়েডে, কিছু KTX লাইব্রেরি নির্দিষ্ট জীবনচক্র ক্লাসের জন্য তাদের নিজস্ব CoroutineScope প্রদান করে। উদাহরণস্বরূপ, ViewModel একটি viewModelScope আছে এবং Lifecycle lifecycleScope আছে। একটি প্রেরক থেকে ভিন্ন, যাইহোক, একটি CoroutineScope coroutines চালায় না।

Android-এ 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()
    }
}

একটি বাতিল সুযোগ আরো coroutines তৈরি করতে পারে না. অতএব, আপনার scope.cancel() কল করা উচিৎ তখনই যখন তার জীবনচক্র নিয়ন্ত্রণকারী ক্লাসটি ধ্বংস হয়ে যাচ্ছে। viewModelScope ব্যবহার করার সময়, ViewModel ক্লাস 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 (...) {
            // Cancel the coroutine started above, this doesn't affect the scope
            // this coroutine was launched in
            job.cancel()
        }
    }
}

Coroutine Context

একটি CoroutineContext নিম্নলিখিত উপাদানগুলির সেট ব্যবহার করে একটি করটিনের আচরণকে সংজ্ঞায়িত করে:

  • Job : করোটিনের জীবনচক্র নিয়ন্ত্রণ করে।
  • CoroutineDispatcher : ডিসপ্যাচগুলি উপযুক্ত থ্রেডে কাজ করে।
  • CoroutineName : coroutine এর নাম, ডিবাগিংয়ের জন্য দরকারী।
  • CoroutineExceptionHandler : ধরা না পড়া ব্যতিক্রমগুলি পরিচালনা করে।

একটি সুযোগের মধ্যে তৈরি করা নতুন coroutines-এর জন্য, নতুন coroutine-এ একটি নতুন Job উদাহরণ বরাদ্দ করা হয়, এবং অন্যান্য CoroutineContext উপাদানগুলি অন্তর্ভুক্ত সুযোগ থেকে উত্তরাধিকার সূত্রে প্রাপ্ত হয়। আপনি launch বা async ফাংশনে একটি নতুন CoroutineContext পাস করে উত্তরাধিকার সূত্রে প্রাপ্ত উপাদানগুলিকে ওভাররাইড করতে পারেন। মনে রাখবেন যে একটি Job launch বা async জন্য কোনও প্রভাব নেই, কারণ 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)
        }
    }
}

অতিরিক্ত coroutines সম্পদ

আরও কোরোটিন সংস্থানগুলির জন্য, নিম্নলিখিত লিঙ্কগুলি দেখুন: