narjes Mansoori
narjes Mansoori
خواندن ۱۶ دقیقه·۱۰ ماه پیش

آشنایی با مفاهیم Coroutine dispatcher,Coroutine builder, Coroutine Scope

سلام بچه ها امیدوارم حالتون خوب باشه

خب باز اومدیم با یه پست دیگه از ادامه پست های مربوط به کوروتین.

خب کوروتین بیلدرها ، توابعی هستند که به شما کمک می‌کنند کوروتین‌ها را به سادگی ایجاد و مدیریت کنید. در واقع، آن‌ها مبدل‌هایی هستند که کار ساخت و اجرای کوروتین‌ها را برای شما آسان‌تر می‌کنند.

نمونه‌هایی از کوروتین بیلدرها عبارتند از:

1. launch

با استفاده از این بیلدر می‌توانید یک کوروتین بدون نیاز به نتیجه بازگشتی ایجاد کنید. به عبارت دیگر، اجرای کوروتین با launch به صورت آسینکرون صورت می‌گیرد و برنامه می‌تواند بلافاصله پس از راه‌اندازی کوروتین به اجرای خود ادامه دهد.

2. async

با استفاده از این بیلدر، می‌توانید یک کوروتین با نیاز به نتیجه بازگشتی ایجاد کنید. کوروتین‌های ساخته شده با async می‌توانند یک مقدار نتیجه را برگردانند که با استفاده از تابع await قابل دریافت است.

3. runBlocking

این بیلدر برای اجرای یک کوروتین در یک بلاک blocking استفاده می‌شود. به عبارت دیگر، با استفاده از runBlocking می‌توانید در توابع blocking مانند تابع main از کوروتین استفاده کنید.

بهتر است با یک مثال عملی کوروتین بیلدرها را توضیح دهم تا بهتر براتون جا بیوفته. در اینجا، یک برنامه ساده را در نظر بگیرید که از دو کوروتین برای اجرای دو تسک مستقل استفاده می‌کند. برای این منظور، از کوروتین بیلدرها launch و async استفاده خواهیم کرد.

fun main() {
// ایجاد یک کوروتین با استفاده از launch
val job1 = CoroutineScope(Dispatchers.Default).launch {
println("شروع کوروتین 1")
delay(1000)
println("پایان کوروتین 1")
}
// ایجاد یک کوروتین با استفاده از async
val deferred = CoroutineScope(Dispatchers.Default).async {
println("شروع کوروتین 2")
delay(2000)
println("پایان کوروتین 2")
"مقدار بازگشتی از کوروتین 2"
}
// انتظار برای پایان اجرای کوروتین 2 و دریافت نتیجه آن
runBlocking {
val result = deferred.await()
println("نتیجه کوروتین 2: $result")
}
println("پایان برنامه")
}

در این مثال، ابتدا یک کوروتین با استفاده از launch ساخته شده است. این کوروتین پس از یک تاخیر ۱ ثانیه‌ای، یک پیام چاپ می‌کند و به پایان می‌رسد.

سپس با استفاده از async، یک کوروتین دیگر ساخته می‌شود. این کوروتین نیز پس از یک تاخیر ۲ ثانیه‌ای، یک پیام چاپ کرده و مقدار "مقدار بازگشتی از کوروتین ۲" را به عنوان نتیجه خروجی تولید می‌کند.

در نهایت، تابع runBlocking برای انتظار و دریافت نتیجه اجرای کوروتین ۲ استفاده می‌شود. سپس پیامی که نتیجه کوروتین ۲ را نشان می‌دهد چاپ می‌شود.

در انتها، پیام "پایان برنامه" چاپ می‌شود.

این مثال نشان می‌دهد که با استفاده از کوروتین بیلدرها launch و async، می‌توانید به راحتی کوروتین‌ها را ایجاد و مدیریت کنید. همچنین، با استفاده از await می‌توانید نتیجه یک کوروتین با نیاز به خروجی را دریافت کنید و در سایر بخش‌های برنامه خود استفاده کنید.

در واقع runBlocking یک کوروتین بیلدر در Kotlin است که به شما اجازه می‌دهد یک بلاک blockingرا اجرا کنید و در عین حال از کوروتین‌ها استفاده کنید.

وقتی از runBlocking استفاده می‌کنید، برنامه شما متوقف می‌شود تا تمامی کوروتین‌ها در بلاک runBlocking اجرا و تکمیل شوند. به عبارت دیگر، runBlocking مانع از اجرای بقیه کد بعد از این بلاک می‌شود تا تمامی کوروتین‌ها به پایان برسند.

در اینجا یک مثال ساده را در نظر بگیرید:

fun main() {
println("شروع برنامه")
runBlocking {
launch {
delay(1000)
println("کوروتین 1")
}
launch {
delay(2000)
println("کوروتین 2")
}
println("کد درون بلاک runBlocking")
}
println("پایان برنامه")
}

در این مثال، ما از runBlocking برای اجرای دو کوروتین استفاده کرده‌ایم. هر کدام از این کوروتین‌ها دارای یک تاخیر هستند و پس از آن یک پیام چاپ می‌کنند.

وقتی برنامه اجرا می‌شود، ابتدا پیام "شروع برنامه" چاپ می‌شود. سپس بلاک runBlocking شروع می‌شود. درون این بلاک، دو کوروتین با استفاده از launch ایجاد شده‌اند. اما زمانی که به سطر runBlocking می‌رسیم، برنامه متوقف می‌شود تا تمامی کوروتین‌ها درون این بلاک به پایان برسند.

در این مثال، هر کوروتین یک تاخیر مختلف دارد. کوروتین اول پس از ۱ ثانیه چاپ می‌کند و کوروتین دوم پس از ۲ ثانیه چاپ می‌کند. سپس پیام "کد درون بلاک runBlocking" چاپ می‌شود. بعد از اتمام بلاک runBlocking، برنامه از حالت متوقف خارج می‌شود و پیام "پایان برنامه" چاپ می‌شود.

خب حالا بریم یه مثال دیگه بزنیم تا بازم بیشتر مطالب بالا رو درک کنیم.

فرض کنید دو تسک طولانی مدت داریم به نام‌های doLongRunningTaskOne و doLongRunningTaskTwo که هرکدام یک نتیجه از نوع عدد صحیح برمی‌گردانند.

private suspend fun doLongRunningTaskOne(): Int {
return withContext(Dispatchers.Default) {
// کد مربوط به وظیفه طولانی مدت اول
// تاخیر مصنوعی برای شبیه‌سازی
delay(2000)
return@withContext 10
}
}
private suspend fun doLongRunningTaskTwo(): Int {
return withContext(Dispatchers.Default) {
// کد مربوط به وظیفه طولانی مدت دوم
// تاخیر مصنوعی برای شبیه‌سازی
delay(2000)
return@withContext 10
}
}

حالا بیایید مثالی از استفاده از این دو تابع ببینیم. در این مثال، ما می‌خواهیم هر دو تسک را اجرا کرده و مجموع نتایج آن‌ها را نمایش دهیم.

GlobalScope.launch(Dispatchers.Main) {
val resultOne = doLongRunningTaskOne()
val resultTwo = doLongRunningTaskTwo()
showResult(resultOne + resultTwo) // اجرا در نخ رابط کاربری (UI)
}

در این کد، با استفاده از GlobalScope.launch و با استفاده از Dispatchers.Main در ترد اصلی یک کوروتین ایجاد می‌کنیم. ما به ترتیب تابع doLongRunningTaskOne و سپس doLongRunningTaskTwo را فراخوانی می‌کنیم و انتظار می‌کشیم تا هر تسک به ترتیب خود به پایان برسد. در نهایت، ما مجموع نتایج را بعد از 4000 میلی ثانیه در ترد رابط کاربری یا ترد اصلی نمایش می‌دهیم.

اما اگر می‌خواهیم هر دو تسک را به صورت همزمان اجرا کنیم، از async استفاده می‌کنیم. زیرا نیاز داریم به نتیجه این وظیفه‌ها دسترسی داشته باشیم.

GlobalScope.launch {
val deferredOne = async {
doLongRunningTaskOne()
}
val deferredTwo = async {
doLongRunningTaskTwo()
}
val result = deferredOne.await() + deferredTwo.await()
showResult(result) // اجرا در نخ رابط کاربری (UI)
}

در این کد، دو کوروتین را با استفاده از async ایجاد می‌کنیم. هر کدام از این کوروتین‌ها نماینده یک وظیفه طولانی مدت هستند. با استفاده از async، هر دو وظیفه به صورت همزمان اجرا می‌شوند. ما با استفاده از deferredOne.await و deferredTwo.await منتظر نتایج هر وظیفه می‌مانیم و سپس به محاسبه جمع آن‌ها می‌پردازیم. در نهایت، نتیجه را بعد از 2000 میلی ثانیه در نخ رابط کاربری نمایش می‌دهیم.

خلاصه‌اش:

  • در واقع launch و async هر دو برای ایجاد کوروتین‌ها استفاده می‌شوند و امکان اجرای همزمان وظایف را فراهم می‌کنند.
  • از async زمانی استفاده می‌شود که نتیجه اجرای یک کوروتین را نیاز داریم دریافت کنیم، در حالی که launch زمانی استفاده می‌شود که نیازی به نتیجه نداشته باشیم.
  • در واقع withContext یک تابع تعلیقی است که برای تغییر ترد اجرای کوروتین فعلی استفاده می‌شود و هیچ کوروتین جدیدی را راه‌اندازی نمی‌کند.خب بیشتر بهت توضیح میدم نگران نباش.😊

پس withContext یک تابع تعلیقی در کتابخانه Coroutines است که برای تغییر محیط اجرای کوروتین استفاده می‌شود. به صورت ساده، می‌توانیم آن را برای تغییرdispatcher (محیط اجرا) کوروتین استفاده کنیم.

بیایید با یک مثال ساده این را بررسی کنیم. فرض کنید که ما دو تابع داریم: fetchDataFromNetwork که اطلاعات را از شبکه دریافت می‌کند و parseData که اطلاعات دریافت شده را پردازش می‌کند. فرض کنید هر کدام از این توابع زمان زیادی برای اجرا نیاز دارند.

suspend fun fetchDataFromNetwork(): String {
return withContext(Dispatchers.IO) {
// کد مربوط به دریافت اطلاعات از شبکه
}
}
suspend fun parseData(data: String): List<String> {
return withContext(Dispatchers.Default) {
// کد مربوط به پردازش اطلاعات
}
}

در این مثال، fetchDataFromNetwork با استفاده از withContext محیط اجرای کوروتین خود را به Dispatchers.IO تغییر می‌دهد. این به این معنی است که این تابع برای اجرا از نخ‌های I/O استفاده می‌کند که به طور خاص برای عملیات ورودی/خروجی شبکه مناسب هستند.

همچنین، parseDataبا استفاده از withContext محیط اجرای کوروتین خود را به Dispatchers.Default تغییر می‌دهد. این به این معنی است که این تابع برای اجرا از نخ‌های پیش‌فرض استفاده می‌کند که برای عملیات محاسباتی مناسب هستند.

پس در نتیجه با استفاده از withContext، ما می‌توانیم محیط اجرای کوروتین را به دلخواه خود تغییر دهیم و کد را در محیط مناسب اجرا کنیم.

امیدوارم دیگه این توضیحات به شما در درک استفاده از withContext کمک کند.



خب حالا بریم ببینیم Scope ها در کوروتین چیا هستند و قرار هست چیکار کنند!!!🤔

در کتابخانه کوروتین ، چندین نوع CoroutineScope وجود دارد که برای مدیریت کوروتین‌ها در محدوده(LifeCycle) مختلف استفاده می‌شوند. در زیر به برخی از انواع اصلی CoroutineScopeاشاره می‌کنم و هرکدام را با مثال توضیح می‌دهم:

  • GlobalScope:

این GlobalScope یک CoroutineScope است که در سراسر برنامه قابل دسترسی است و از طریق آن می‌توانید کوروتین‌ها را ایجاد و مدیریت کنید. این نوع CoroutineScope در برنامه‌های کوچک و ساده معمولاً مورد استفاده قرار می‌گیرد. اما بهتر است از استفاده از GlobalScope خودداری کنید و از ایجاد CoroutineScope محدودتر در محدوده مورد نظر استفاده کنید.

بیایید با هم یه مثال ببینیم :

fun main() {
println("شروع برنامه")
// ایجاد یک کوروتین در GlobalScope
GlobalScope.launch {
delay(1000)
println("کوروتین")
}
println("پایان برنامه")
}

CoroutineScope:

شما می‌توانید یک Scopeمحدود بر اساس یک منطقه خاص در برنامه‌تان ایجاد کنید. این نوع CoroutineScope معمولاً برای محدوده‌های مشخص در برنامه مفید است و به شما امکان می‌دهد که کوروتین‌ها را در این محدوده ایجاد و مدیریت کنید.

fun main() {
println("شروع برنامه")
// ایجاد یک CoroutineScope محدود
val scope = CoroutineScope(Dispatchers.Default)
// ایجاد کوروتین در داخل CoroutineScope
scope.launch {
delay(1000)
println("کوروتین")
}
println("پایان برنامه")
}
  • ViewModelScope:

در واقع ViewModelScope یک نوع خاص از CoroutineScope است که برای استفاده در کلاسهای ViewModel در پروژه‌های Android طراحی شده است. با استفاده از ViewModelScopeمی‌توانید کوروتین‌ها را در محدوده ViewModel ایجاد و مدیریت کنید. ViewModelScope خود به طور پیش‌فرض از Dispatchers.Mainاستفاده می‌کند.

مثال:

class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
delay(1000)
println("اطلاعات دریافت شد")
}
}
}

در این مثال، ما یک ViewModel به نام MyViewModel داریم که دارای یک تابع fetchData است. در داخل این تابع، ما از viewModelScopeاستفاده می‌کنیم تا یک کوروتین را در محدوده ViewModelایجاد کنیم و پس از تاخیر ۱ ثانیه، یک پیام چاپ کنیم.

  • lifecyclescop

در واقع LifecycleScope یک نوع CoroutineScope خاص است که برای استفاده در پروژه‌های Androidو هماهنگی با دوره حیات (Lifecycle) مورد استفاده قرار می‌گیرد. این نوع CoroutineScopeبا استفاده از Lifecycle مربوطه، مدیریت مناسبی برای کوروتین‌ها در طول عمر مرتبط با Lifecycleفراهم می‌کند.

زمانی که یک کلاس از LifecycleOwner را پیاده‌سازی می‌کند (مانند Activity یا Fragment)، می‌توان از LifecycleScope استفاده کرد. LifecycleScopeبه طور پیش‌فرض از Dispatchers.Main استفاده می‌کند.

یک مثال ساده از استفاده از LifecycleScope در یک Activity:

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// استفاده از LifecycleScopeدر Activity
lifecycleScope.launch {
delay(1000)
println("کوروتین")
}
}
}


در این مثال، ما یک Activity به نام MainActivity داریم که از AppCompatActivity ارث‌بری می‌کند. در داخل تابع onCreate، ما از lifecycleScope استفاده می‌کنیم تا یک کوروتین در محدوده Lifecycle ایجاد کنیم. در اینجا، پس از تاخیر ۱ ثانیه، یک پیام چاپ می‌شود.

با استفاده از LifecycleScope، کوروتین‌ها متناسب با دوره حیات مربوطه مدیریت می‌شوند. به عنوان مثال، اگر Activityمتوقف شود یا از حالت مخفی به حالت قابل مشاهده برگردد، کوروتین‌های مرتبط با آن به توقف و از سرگیری خودکار می‌شوند.

استفاده از LifecycleScope به شما امکان می‌دهد که کوروتین‌ها را با توجه به دوره حیات مربوطه مدیریت کنید و از مشکلات مربوط به شروع و اتمام مناسب کوروتین‌ها در زمان‌های نامناسب جلوگیری کنید.

به طور کلی، CoroutineScope و lifecycleScope هر دو روش هایی هستند که به شما امکان می‌دهند کوروتین‌ها را در برنامه خود ایجاد کنید و مدیریت کنید. اما تفاوت اصلی بین آنها در دامنه زمانی فعالیت کوروتین‌ها است. نوع CoroutineScope به شما اجازه می‌دهد دامنه زمانی دلخواه خود را تعیین کنید، در حالی که lifecycleScope به صورت خودکار با دوره عمر (lifecycle) مربوط به کلاسی که در آن استفاده می‌کنید، هماهنگ می‌شود.

در مثال زیر، تفاوت بین CoroutineScope و lifecycleScope را بررسی می‌کنیم:

class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)

// استفاده از CoroutineScope
val myScope = CoroutineScope(Dispatchers.Main)
myScope.launch {
// کوروتین‌ها در دامنه زمانی CoroutineScope اجرا می‌شوند
doTaskOne()
doTaskTwo()
}

// استفاده از lifecycleScope
lifecycleScope.launch {
// کوروتین‌ها در دامنه زمانی lifecycleScope اجرا می‌شوند
doTaskOne()
doTaskTwo()
}
}

override fun onDestroy() {
super.onDestroy()

// لغو تمام کوروتین‌های موجود در دامنه زمانی CoroutineScope
myScope.cancel()
}

private suspend fun doTaskOne() {
// عملیات طولانی مدت
}

private suspend fun doTaskTwo() {
// عملیات طولانی مدت
}
}

در این مثال، MyActivity یک فعالیت (Activity) در اندروید است که از CoroutineScope استفاده می‌کند. در onCreate، دو کوروتین با استفاده از CoroutineScope ایجاد می‌شوند و در دامنه زمانی CoroutineScope اجرا می‌شوند.

از طرف دیگر، lifecycleScope استفاده شده در بلوک lifecycleScope.launch، با دوره عمر فعالیت (MyActivity) هماهنگ می‌شود. این به این معنی است که کوروتین‌های درون lifecycleScope به طور خودکار لغو می‌شوند و منابع آنها را آزاد می‌کنند هنگامی که فعالیت منتهی می‌شود.

در onDestroy، ما کوروتین‌های موجود در دامنه زمانی CoroutineScope را با فراخوانی myScope.cancel() لغو می‌کنیم، در حالی که کوروتین‌های موجود در lifecycleScope به طور خودکار لغو می‌شوند.

بنابراین، تفاوت اصلی بین CoroutineScope و lifecycleScope در مدیریت دامنه زمانی کوروتین‌ها است. CoroutineScope به شما اجازه می‌دهد دامنه زمانی دلخواهخود را تعیین کنید و باید به صورت دستی کوروتین‌ها را لغو کنید. از طرف دیگر، lifecycleScope به صورت خودکار با دوره عمر فعالیت هماهنگ می‌شود و کوروتین‌ها به طور خودکار لغو می‌شوند هنگامی که فعالیت به پایان می‌رسد.




حالا بریم ببینیم Dispatchers ها چیا هستند!!🤔🤔

در کتابخانه کوروتین Dispatchers مسئول مدیریت و ارسال کوروتین‌ها به نخ‌های مختلف است. Dispatchers به شما امکان می‌دهد که کوروتین‌ها را در نخ‌های مختلف اجرا کنید، از جمله نخ اصلی (Main)، نخ‌های پس‌زمینه(Background) و نخ‌های IO.

زیر مجموعه‌های مختلفی از Dispatchers درKotlin وجود دارد، که هر کدام برای مورد استفاده خاصی طراحی شده‌اند. در زیر، به برخی از Dispatchers اصلی اشاره می‌کنم و هر کدام را با مثال توضیح می‌دهم:

  • Dispatchers.Main:

از Dispatchers.Main برای اجرای کوروتین‌ها در نخ اصلی (Main Thread) استفاده می‌شود. این نخ اصلی برای انجام عملیات‌های رابط کاربری (UI) در پروژه‌های Android بسیار مهم است. استفاده از Dispatchers.Main در کوروتین‌ها از بروز خطاهای مربوط به تغییرات رابط کاربری در نخ نامناسب جلوگیری می‌کند.

مثال:

fun main() {
println("شروع برنامه")
// اجرای کوروتین در نخ اصلی
runBlocking(Dispatchers.Main) {
delay(1000)
println("کوروتین")
}
println("پایان برنامه")
}
  • Dispatchers.Default:

از Dispatchers.Default برای اجرای کوروتین‌ها در نخ‌های پس‌زمینه (Background Thread) استفاده می‌شود. این نوع از Dispatchers برای عملیات‌های محاسباتی متوسط ​​تا سنگین مناسب است. معمولاً برای اجرای عملیات همگام سنگین مانند محاسبات پیچیده استفاده می‌شود.

مثال:

fun main() {
println("شروع برنامه")
// اجرای کوروتین در نخ پس‌زمینه
runBlocking(Dispatchers.Default) {
delay(1000)
println("کوروتین")
}
println("پایان برنامه")
}
  • Dispatchers.IO:

از Dispatchers.IO برای اجرای کوروتین‌ها در نخ‌های ورودی/خروجی (IO Thread) استفاده می‌شود. این نوع از Dispatchers برای عملیات ورودی/خروجی شبکه، فایل، دیتابیس و سایر عملیات ورودی/خروجی طولانی مدت مناسب است. در اینجا، کوروتین‌ها در نخ‌های پس‌زمینه اجرا می‌شوند تا از بلاک شدن نخ اصلی جلوگیری کنند.

مثال:

fun main() {
println("شروع برنامه")
// اجرای کوروتین در نخ ورودی/خروجی
runBlocking(Dispatchers.IO) {
delay(1000)
println("کوروتین")
}
println("پایان برنامه")
}

این مثال‌ها نشان می‌دهند که با استفاده از Dispatchers مختلف ، می‌توانید کوروتین‌ها را در نخ‌های مختلف اجرا کنید و بهترین عملکرد و عدم بلاک شدن نخ اصلی را داشته باشید. با انتخاب صحیحDispatchers مناسب برای نوع عملیاتی که قصد انجام آن را دارید، می‌توانید کارایی و پاسخگویی برنامه خود را بهبود بخشید.

به عنوان نکته باید بگم که کوروتین‌ها در زبان Kotlin از نوعStackless هستند، به این معنی که هر کوروتین به طور خودکار از یک استک جداگانه استفاده نمی‌کند. در عوض، از مکانیزمی به نام"Continuation Passing Style" (CPS) برای مدیریت و انتقال ادامه کار بین کوروتین‌ها استفاده می‌کند.

درCPS، هر کوروتین یک شیء به نام"Continuation" را به عنوان ورودی دریافت می‌کند. این Continuation شامل ادامه کاری است که باید پس از اتمام کوروتین اجرا شود. به این ترتیب، هنگامی که یک کوروتین متوقف می‌شود، Continuation مربوطه به کوروتین بعدی منتقل می‌شود و اجرای برنامه از همان نقطه ادامه می‌یابد.

اگر کوروتین‌هاStackful بودند، در هنگام اجرای تابعmyCoroutine()، یک استک جداگانه برای آن ایجاد می‌شد و توابع برنامه در تابع myCoroutine() به صورت پشته‌ای در هم قرار می‌گرفتند. اما با استفاده از CPS، هر کوروتین تنها یک Continuation را دریافت می‌کند و اجرای برنامه بین کوروتین‌ها منتقل می‌شود.

استفاده از کوروتین‌های Stackless مزایایی مانند کارایی بالا، مدیریت مناسب حافظه و عدم احتمال بروز Deadlock و Stack Overflow را به همراه دارد.




آشنایی با Deferred,Job و متدهای Join , JoinAll , await,cancel , isCancelled isCompleted, getCompleted

خب حالا بریم سراغ توابعی که هنگام کار با کوروتین ها لازممون میشه.در حقیقت، launch و async توابعی هستند که کوروتین‌ها را شروع می‌کنند و خودشان مستقیماً توابع خاصی ندارند که فقط روی آن‌ها صدا زده شوند. اما، آن‌ها چیزهایی را برمی‌گردانند (مثلاً Job و Deferred<T> به ترتیب) که می‌توان با آن‌ها کارهایی انجام داد.

برای launch:

تابع launch یک Job برمی‌گرداند. روی این Job می‌توان توابع زیر را صدا زد:

تابع cancel(): برای کنسل کردن کوروتین.

تابع join(): منتظر می‌ماند تا کوروتین مربوطه کامل شود.

تابع isCancelled: برای بررسی که آیا کوروتین کنسل شده است یا خیر.

تابع isActive: بررسی می‌کند که آیا کوروتین هنوز فعال است.

برای async:

از async یک Deferred<T> برمی‌گرداند که یک نوع خاص از Job است و علاوه بر توابع موجود در Job، توابع زیر را نیز دارد:

تابع await(): منتظر می‌ماند تا نتیجه (یا خطا) از عملیات آسنکرون برگردد.

تابع isCompleted: بررسی می‌کند که آیا عملیات آسنکرون کامل شده است.

تابع getCompleted(): اگر عملیات کامل شده باشد، نتیجه را بلافاصله برمی‌گرداند.

نکته مهم:

این توابع و خاصیت‌ها مستقیماً به launch و async تعلق ندارند، بلکه به اشیاء برگشت داده شده از این توابع (یعنی Job و Deferred<T>) تعلق دارند. این نکته مهمی است زیرا launch و async خودشان تنها کوروتین‌ها را شروع می‌کنند و مدیریت زندگی کوروتین‌ها از طریق اشیاء برگردانده شده از آن‌ها انجام می‌گیرد.

خب حالا بریم یک مثال کلی برای درک بهتر این توابع بزنیم 😍😍


fun main() {
val job1: Job = GlobalScope.launch {
delay(1000)
println("Job 1 is complete")
}

val deferred1: Deferred<String> = GlobalScope.async {
delay(1500)
return@async "Deferred 1 result"
}
val deferred2: Deferred<String> = GlobalScope.async {
delay(2000)
return@async "Deferred 2 result"
}
runBlocking {
job1.join()
println("Job 1 has joined")
val result1 = deferred1.await()
println("Deferred 1 result: $result1")
val result2 = deferred2.await()
println("Deferred 2 result: $result2")
val job2: Job = launch {
delay(500)
println("Job 2 is complete")
}
val job3: Job = launch {
delay(700)
println("Job 3 is complete")
}
val job4: Job = launch {
delay(900)
println("Job 4 is complete")
}
joinAll(job2, job3, job4)
println("All jobs have completed")
job2.cancel()
if (job2.isCancelled) {
println("Job 2 is cancelled")
}
if (job2.isCompleted) {
println("Job 2 is completed")
}
val completedJobs: List<Job> = listOf(job1, job2, job3, job4).filter { it.isCompleted }
println("Completed jobs: $completedJobs")
val completedResults: List<String> = listOf(deferred1, deferred2).filter { it.isCompleted }.map { it.getCompleted() }
println("Completed results: $completedResults")
}
}

در این مثال، ابتدا یک Job با استفاده از launch ایجاد می‌شود که دارای تاخیر 1 ثانیه است. سپس دو Deferred با استفاده از async ایجاد می‌شوند که هر کدام دارای تاخیر‌های مختلفی هستند. سپس با استفاده از join و await منتظر اتمام هر Job و Deferred می‌مانیم و نتایج را دریافت می‌کنیم.

سپس سه Job دیگر با استفاده از launch ایجاد می‌شوند و با استفاده از joinAll منتظر اتمام همه آن‌ها می‌مانیم. سپس با استفاده از cancel و isCancelled بررسی می‌کنیم که آیا یک Job لغو شده است یا خیر. همچنین با استفاده از isCompleted بررسی می‌کنیم که آیا یک Job تکمیل شده است یا خیر.

در آخر، با استفاده از filter و map، Jobها و Deferredهایی که تکمیل شده‌اند را انتخاب و نتایج آن‌ها را دریافت می‌کنیم. خروجی این مثال به صورت زیر خواهد بود:

Job 1 is complete
Job 1 has joined
Deferred 1 result: Deferred 1 result
Deferred 2 result: Deferred 2 result
Job 2 is complete
Job 3 is complete
Job 4 is complete
All jobs have completed
Job 2 is cancelled
Completed jobs: [Job(active), Job#e768, Job#4d6c]
Completed results: [Deferred 1 result, Deferred 2 result]

در قسمت `Completed jobs`، شناسه‌های `Job`هایی که تکمیل شده‌اند نشان داده شده‌اند. همچنین در قسمت `Completed results`، نتایج `Deferred`هایی که تکمیل شده‌اند نشان داده شده‌اند.

امیدوارم براتون مفید واقع شده باشه.پیشنهاد میکنم پست بعدی هم مطالعه کنید

مدیریت خطاها در کوروتین ها





coroutinesjob
Android Developer
شاید از این پست‌ها خوشتان بیاید