কোটলিন: সাথে কনটেক্সট () বনাম অ্যাসিঙ্ক-প্রতীক্ষা


91

আমি কোটলিন ডক্স পড়ছি , এবং যদি আমি সঠিকভাবে বুঝতে পারি তবে কোটলিনের দুটি ফাংশন নিম্নলিখিত হিসাবে কাজ করছে:

  1. withContext(context): প্রদত্ত ব্লকটি কার্যকর হলে, কর্টিন পূর্ববর্তী প্রসঙ্গে ফিরে যায় the
  2. async(context): প্রদত্ত প্রসঙ্গে একটি নতুন কর্টিন শুরু করে এবং আমরা যদি .await()ফিরে Deferredআসা টাস্কটির দিকে কল করি তবে এটি কলিং কর্টিন স্থগিত করবে এবং যখন ক্র্যাঙ্কড কর্টিনের অভ্যন্তরের ব্লকটি কার্যকর করবে তখন পুনরায় শুরু হবে।

নিম্নলিখিত দুটি সংস্করণের জন্য এখন code:

সংস্করণ 1:

  launch(){
    block1()
    val returned = async(context){
      block2()
    }.await()
    block3()
  }

সংস্করণ 2:

  launch(){
    block1()
     val returned = withContext(context){
      block2()
    }
    block3()
  }
  1. ব্লক 1 () উভয় সংস্করণে, ব্লক 3 () ডিফল্ট প্রসঙ্গে (কমনপুল?) কার্যকর করা হয় যেখানে ব্লক 2 () প্রদত্ত প্রসঙ্গে কার্যকর করা হয়।
  2. ব্লক 1 () -> ব্লক 2 () -> ব্লক 3 () অর্ডারের সাথে সামগ্রিক সম্পাদনটি সিঙ্ক্রোনাস।
  3. কেবলমাত্র তফাত আমি দেখতে পাচ্ছি হ'ল সংস্করণ 1 আরেকটি কর্টিন তৈরি করে, যেখানে সংস্করণ 2 প্রসঙ্গে স্যুইচ করার সময় কেবল একটি কর্টিন প্রয়োগ করে।

আমার প্রশ্নগুলি হ'ল:

  1. এটি কার্যত একই রকমের withContextচেয়ে ব্যবহার করা কি সর্বদা ভাল নয় async-await, তবে অন্য কোনও কর্টিন তৈরি করে না। প্রচুর সংখ্যক করটিইন যদিও হালকা ওজনের, তবুও অ্যাপ্লিকেশনগুলির দাবি করতে সমস্যা হতে পারে।

  2. এর async-awaitচেয়ে বেশি কেস আছে withContext?

আপডেট: কোটলিন 1.2.50 এর এখন একটি কোড পরিদর্শন রয়েছে যেখানে এটি রূপান্তর করতে পারে async(ctx) { }.await() to withContext(ctx) { }


আমি মনে করি আপনি যখন ব্যবহার করবেন তখন withContextনির্বিশেষে একটি নতুন কর্টিন তৈরি হয়। এটি আমি সোর্স কোড থেকে দেখতে পাচ্ছি।
stdout

@stdout async/awaitওপি অনুসারে, নতুন কর্টিনও তৈরি করে না ?
ইগোরগানাপলস্কি

উত্তর:


126

প্রচুর সংখ্যক করটিইন যদিও হালকা ওজনের, তবুও অ্যাপ্লিকেশনগুলির দাবি করতে সমস্যা হতে পারে

আমি তাদের সমস্যাটির প্রকৃত ব্যয়ের পরিমাণ নির্ধারণ করে একটি সমস্যা হিসাবে "অনেকগুলি কর্টিন" এই কল্পকাহিনীটি দূর করতে চাই।

প্রথমত, আমাদের যে কর্টিন প্রসঙ্গে এটি সংযুক্ত রয়েছে তার থেকে কর্টিন নিজেই বিচ্ছিন্ন করা উচিত । আপনি এভাবে ন্যূনতম ওভারহেড সহ কেবল একটি কর্টিন তৈরি করেন:

GlobalScope.launch(Dispatchers.Unconfined) {
    suspendCoroutine<Unit> {
        continuations.add(it)
    }
}

এই অভিব্যক্তিটির মান Jobহ'ল একটি স্থগিত কর্টিন। ধারাবাহিকতা ধরে রাখতে, আমরা এটিকে বিস্তৃত ক্ষেত্রের তালিকায় যুক্ত করেছি।

আমি এই কোডটি বেঞ্চমার্ক করেছি এবং উপসংহারে এসেছি যে এটি 140 বাইট বরাদ্দ করে এবং 100 টি ন্যানোসেকেন্ডগুলি সম্পূর্ণ করতে লাগে । সুতরাং এটি কোনও হালকা ওজনের একটি কর্টিন।

প্রজননযোগ্যতার জন্য, আমি এই কোডটি ব্যবহার করেছি:

fun measureMemoryOfLaunch() {
    val continuations = ContinuationList()
    val jobs = (1..10_000).mapTo(JobList()) {
        GlobalScope.launch(Dispatchers.Unconfined) {
            suspendCoroutine<Unit> {
                continuations.add(it)
            }
        }
    }
    (1..500).forEach {
        Thread.sleep(1000)
        println(it)
    }
    println(jobs.onEach { it.cancel() }.filter { it.isActive})
}

class JobList : ArrayList<Job>()

class ContinuationList : ArrayList<Continuation<Unit>>()

এই কোডটি কর্টিনগুলির একটি গোছা শুরু করে এবং তারপরে ঘুমায় যাতে আপনার কাছে ভিজুয়ালভিএম এর মতো একটি মনিটরিং সরঞ্জাম সহ গাদা বিশ্লেষণ করার সময় হয়। আমি বিশেষায়িত ক্লাস তৈরি করেছি JobListএবং ContinuationListকারণ এটি হিপ ডাম্প বিশ্লেষণ করা সহজ করে তোলে।


আরো একটি সম্পূর্ণ গল্প পেতে, আমি নিচের কোড ব্যবহার করা এছাড়াও খরচ পরিমাপ withContext()এবং async-await:

import kotlinx.coroutines.*
import java.util.concurrent.Executors
import kotlin.coroutines.suspendCoroutine
import kotlin.system.measureTimeMillis

const val JOBS_PER_BATCH = 100_000

var blackHoleCount = 0
val threadPool = Executors.newSingleThreadExecutor()!!
val ThreadPool = threadPool.asCoroutineDispatcher()

fun main(args: Array<String>) {
    try {
        measure("just launch", justLaunch)
        measure("launch and withContext", launchAndWithContext)
        measure("launch and async", launchAndAsync)
        println("Black hole value: $blackHoleCount")
    } finally {
        threadPool.shutdown()
    }
}

fun measure(name: String, block: (Int) -> Job) {
    print("Measuring $name, warmup ")
    (1..1_000_000).forEach { block(it).cancel() }
    println("done.")
    System.gc()
    System.gc()
    val tookOnAverage = (1..20).map { _ ->
        System.gc()
        System.gc()
        var jobs: List<Job> = emptyList()
        measureTimeMillis {
            jobs = (1..JOBS_PER_BATCH).map(block)
        }.also { _ ->
            blackHoleCount += jobs.onEach { it.cancel() }.count()
        }
    }.average()
    println("$name took ${tookOnAverage * 1_000_000 / JOBS_PER_BATCH} nanoseconds")
}

fun measureMemory(name:String, block: (Int) -> Job) {
    println(name)
    val jobs = (1..JOBS_PER_BATCH).map(block)
    (1..500).forEach {
        Thread.sleep(1000)
        println(it)
    }
    println(jobs.onEach { it.cancel() }.filter { it.isActive})
}

val justLaunch: (i: Int) -> Job = {
    GlobalScope.launch(Dispatchers.Unconfined) {
        suspendCoroutine<Unit> {}
    }
}

val launchAndWithContext: (i: Int) -> Job = {
    GlobalScope.launch(Dispatchers.Unconfined) {
        withContext(ThreadPool) {
            suspendCoroutine<Unit> {}
        }
    }
}

val launchAndAsync: (i: Int) -> Job = {
    GlobalScope.launch(Dispatchers.Unconfined) {
        async(ThreadPool) {
            suspendCoroutine<Unit> {}
        }.await()
    }
}

এটি উপরের কোডটি থেকে পাওয়া টিপিক্যাল আউটপুট:

Just launch: 140 nanoseconds
launch and withContext : 520 nanoseconds
launch and async-await: 1100 nanoseconds

হ্যাঁ, async-awaitপ্রায় দ্বিগুণ সময় লাগে withContextতবে এটি এখনও একটি মাইক্রোসেকেন্ড ond আপনার অ্যাপ্লিকেশনটিতে "সমস্যা" হওয়ার জন্য আপনাকে এগুলি একটি শক্ত লুপে চালু করতে হবে, ছাড়াও প্রায় কিছুই করতে হবে না।

ব্যবহার করে measureMemory()আমি কলটিতে নিম্নলিখিত মেমরির দাম খুঁজে পেয়েছি:

Just launch: 88 bytes
withContext(): 512 bytes
async-await: 652 bytes

এর ব্যয়টি async-awaitহ'ল 140 বাইটের চেয়ে বেশি withContext, আমরা একটি কর্টিনের মেমরির ওজন হিসাবে পেয়েছি। এটি CommonPoolপ্রসঙ্গ স্থাপনের সম্পূর্ণ ব্যয়ের একটি অংশ মাত্র ।

তাহলে কর্মক্ষমতা / মেমরি প্রভাব মধ্যে ফয়সালা করার একমাত্র নির্ণায়ক ছিল withContextএবং async-await, উপসংহার সেখানে বাস্তব ব্যবহারের ক্ষেত্রে 99% তাদের মধ্যে কোন প্রাসঙ্গিক পার্থক্য যে হতে হবে।

আসল কারণ হ'ল withContext()একটি সহজ এবং আরও সরাসরি API, বিশেষত ব্যতিক্রম পরিচালনার ক্ষেত্রে:

  • একটি ব্যতিক্রম যা এর মধ্যে পরিচালিত হয় না async { ... }তার পিতামাতার কাজ বাতিল হয়ে যায়। আপনি ম্যাচ থেকে ব্যতিক্রমগুলি কীভাবে পরিচালনা করবেন তা নির্বিশেষে এটি ঘটে await()। আপনি যদি coroutineScopeএটির জন্য কোনও প্রস্তুতি না নিয়ে থাকেন তবে এটি আপনার সম্পূর্ণ অ্যাপ্লিকেশনটি নামিয়ে আনতে পারে।
  • withContext { ... }কেবলমাত্র withContextকলটি দিয়ে হ্যান্ডেল না করা ব্যতিক্রম , আপনি এটিকে অন্য যেকোন মত হ্যান্ডেল করেন।

withContext আপনি প্যারেন্ট কাউটিন স্থগিত করে এবং সন্তানের জন্য অপেক্ষা করছেন এই সত্যটি কাজে লাগিয়ে অপ্টিমাইজড হওয়ার ঘটনা ঘটে but তবে এটি কেবল একটি যুক্ত বোনাস।

async-awaitআপনি যেখানে প্রকৃত সম্মতি চান সে ক্ষেত্রে তাদের সংরক্ষণ করা উচিত, যাতে আপনি ব্যাকগ্রাউন্ডে বেশ কয়েকটি কর্টিন চালু করেন এবং কেবল তখনই তাদের জন্য অপেক্ষা করুন। সংক্ষেপে:

  • async-await-async-await - না, ব্যবহার withContext-withContext
  • async-async-await-await - এটি এটি ব্যবহার করার উপায়।

অতিরিক্ত মেমরির ব্যয় সম্পর্কে async-await: আমরা যখন ব্যবহার করি তখন withContextএকটি নতুন কর্টিনও তৈরি করা হয় (সোর্স কোড থেকে আমি যতদূর দেখতে পাচ্ছি) সুতরাং আপনি কী ভাবেন যে পার্থক্যটি অন্য কোথাও থেকে এসেছে?
stdout

4
@stdout আমি যখন এই পরীক্ষাগুলি চালিয়েছি তখন থেকেই গ্রন্থাগারটি বিকশিত হচ্ছে। উত্তরের কোডটি পুরোপুরি স্ব-অন্তর্ভুক্ত বলে মনে করা হচ্ছে, যাচাই করতে আবার এটি চালানোর চেষ্টা করুন। asyncএকটি Deferredবস্তু তৈরি করে , যা কিছু পার্থক্য ব্যাখ্যা করতে পারে।
মার্কো টপলনিক

The " ধারাবাহিকতা ধরে রাখতে "। কখন আমাদের এটি ধরে রাখতে হবে?
ইগোরগানাপলস্কি

4
@ আইগরগানাপলস্কি এটি সর্বদা বজায় থাকে তবে সাধারণত ব্যবহারকারীর কাছে দৃশ্যমান হয় না। ধারাবাহিকতা হারাতে সমান Thread.destroy()- পাতলা বাতাসে কার্যকর হওয়া কার্যকর করা।
মার্কো টপলনিক

22

এটি বিনোদনের সাথে অনুরূপ হওয়ার চেয়ে কনসেক্সট-এর সাথে সর্বদা ব্যবহার করা ভাল না কারণ এটি মজাদারভাবে একই রকম, তবে অন্য কোনও কর্টিন তৈরি করে না। বৃহত্তর নাম্বার কর্টাইনগুলি, যদিও হালকা ওজনের অ্যাপ্লিকেশনগুলির দাবিতে সমস্যা হতে পারে

কনটেক্সট-এর সাথে এমন কোনও মামলা রয়েছে যা অ্যাসিঞ্চ-অপেক্ষার চেয়ে ভাল

আপনি যখন এক সাথে একাধিক কাজ সম্পাদন করতে চান তখন আপনার async ব্যবহার / প্রতীক্ষা করা উচিত, উদাহরণস্বরূপ:

runBlocking {
    val deferredResults = arrayListOf<Deferred<String>>()

    deferredResults += async {
        delay(1, TimeUnit.SECONDS)
        "1"
    }

    deferredResults += async {
        delay(1, TimeUnit.SECONDS)
        "2"
    }

    deferredResults += async {
        delay(1, TimeUnit.SECONDS)
        "3"
    }

    //wait for all results (at this point tasks are running)
    val results = deferredResults.map { it.await() }
    println(results)
}

যদি আপনাকে একযোগে একাধিক কাজ চালানোর প্রয়োজন না হয় তবে আপনি কনটেক্সট দিয়ে ব্যবহার করতে পারেন।


13

সন্দেহ হলে এটিকে থাম্বের নিয়মের মতো মনে রাখবেন:

  1. যদি একাধিক কাজ সমান্তরাল এবং চূড়ান্ত ফলাফলের মধ্যে ঘটতে হয় তবে সেগুলি সমস্ত সম্পূর্ণ করার উপর নির্ভর করে, তবে ব্যবহার করুন async

  2. একটি একক কাজের ফলাফল ফেরত দেওয়ার জন্য, ব্যবহার করুন withContext


4
উভয় asyncএবং withContextএকটি স্থগিত সুযোগ মধ্যে অবরুদ্ধ?
ইগোরগানাপলস্কি

4
@ ইগোরগানাপলস্কি যদি আপনি মূল থ্রেড ব্লক করার কথা বলছেন, asyncএবং মূল থ্রেডটি অবরুদ্ধ withContextকরবেন না, তবে দীর্ঘকালীন চলমান কোনও কাজ চলাকালীন এবং ফলাফলের জন্য অপেক্ষা করতে করতে তারা কেবল কর্টিনের দেহকে স্থগিত করবে। আরও তথ্য এবং উদাহরণের জন্য মিডিয়াম এই নিবন্ধটি দেখুন: কোটলিন করোটিনগুলির সাথে অ্যাসিঙ্ক অপারেশনস
যোগেশ उमেশ ভৈতন্য
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.