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

آشنایی کامل با مفاهیم supervisorScope و supervisorJob در کوروتین ها کوروتین ها

  1. خب در پست قبلی در مورد مدیریت exception ها در کوروتین ها صحبت کردیم .حالا سوالی مطرح بود که اگر در حین اجرای کوروتین ها برای هر کوروتین خطایی اتفاق بیوفتد وضعیت بقیه کوروتین ها به چه صورت میباشد؟پس لازم هست اینجا با مفاهیم supervisorScope و supervisorJob آشنا شویم .

الف-:coroutineScope یک suspend function است که یک بلاک کد را به عنوان پارامتر می‌گیرد و یک کوروتین را ایجاد می‌کند.خب در مورد coroutineScope قبلا هم صحبت کرده بودیم .بنابرین
وظیفه‌ی coroutineScope این است که تا زمانی که کلیه‌ی کوروتین‌های داخلی اجرا نشده‌اند، اجازه نمی‌دهد که خروجی داده شود.
اگر یکی از کوروتین‌های داخلی با خطا روبرو شود، تمامی کوروتین‌های دیگر در coroutineScope لغو می‌شوند و این کوروتین همچنین با یک CancellationException کنسل می‌شود.

  1. ب- :supervisorScope نیز مانند coroutineScope یک suspend function است که یک بلاک کد را به عنوان پارامتر می‌گیرد و یک کوروتین را ایجاد می‌کند. با این تفاوت که supervisorScope اجازه می‌دهد که کوروتین‌های داخلی به طور مستقل از یکدیگر اجرا شوند. به این معنی که اگر یکی از کوروتین‌های داخلی با خطا مواجه شود، فقط آن کوروتین لغو می‌شود و بقیه ادامه می‌یابند.

با استفاده از این دو تابع، می‌توانید رفتار مناسبی برای مدیریت خطاها و اجرای کوروتین‌ها در برنامه‌ی خود ایجاد کنید.

خب حالا مثال بزنیم که بیشتر درک کنیم :

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


suspend fun main() {
try {
coroutineScope {
val job1 = launch {
delay(1000)
println("Task 1 completed")
}

val job2 = launch {
delay(2000)
println("Task 2 completed")
}

// Wait for all child coroutines to complete
job1.join()
job2.join()

println("All tasks completed successfully")
}
} catch (e: Exception) {
println("An error occurred: ${e.message}")
}
}

خروجی کد بالا به صورت زیر میباشد :

Task 1 completed

Task 2 completed

All tasks completed successfully

در این کد، دو کوروتین (job1 و job2) در یک coroutineScope اجرا می‌شوند. ابتدا کوروتین job1 با یک تأخیر 1000 میلی‌ثانیه اجرا شده و پس از آن پیام "Task 1 completed" را چاپ می‌کند. سپس کوروتین job2 با یک تأخیر 2000 میلی‌ثانیه اجرا شده و پس از آن پیام "Task 2 completed" را چاپ می‌کند. سپس با استفاده از join() انتظار می‌رود تا هر دو کوروتین به پایان برسند و پیام "All tasks completed successfully" چاپ می‌شود.

حالا اگر یکی از کوروتین‌ها با خطا مواجه شود، کل بلاک کد coroutineScope به طور کامل لغو می‌شود:

suspend fun main() {
try {
coroutineScope {
val job1 = launch {
delay(1000)
println("Task 1 completed")
}

val job2 = launch {
delay(2000)
println("Task 2 completed")
throw Exception("Error in task 2")
}

// Wait for all child coroutines to complete
job1.join()
job2.join()

println("All tasks completed successfully")
}
} catch (e: Exception) {
println("An error occurred: ${e.message}")
}
}

حالا خروجی کد زیر به صورت زیر است :

Task 1 completed
Task 2 completed
An error occurred: Error in task 2

در این کد، دو کوروتین (job1 و job2) در یک coroutineScope اجرا می‌شوند. ابتدا کوروتین job1 با یک تأخیر 1000 میلی‌ثانیه اجرا شده و پس از آن پیام "Task 1 completed" را چاپ می‌کند. سپس کوروتین job2 با یک تأخیر 2000 میلی‌ثانیه اجرا شده و پیام "Task 2 completed" را چاپ می‌کند. اما سپس یک استثناء (Exception) در job2 پرتاب می‌شود با پیام "Error in task 2".

زمانی که استثناء در job2 پرتاب می‌شود، اجرای کد از داخل coroutineScope خارج شده و به بلاک catch که در main تعریف شده است منتقل می‌شود. از طریق این بلاک catch، پیام "An error occurred: Error in task 2" چاپ می‌شود.

حالا اگر می‌خواهید تنها کوروتینی که با خطا مواجه شده است را لغو کنید و سایر کوروتین‌ها ادامه دهند، از supervisorScope استفاده کنید:

suspend fun main() {
try {
supervisorScope {
val job1 = launch {
delay(1000)
println("Task 1 completed")
}

val job2 = launch {
delay(2000)
println("Task 2 completed")
throw Exception("Error in task 2")
}

// Wait for all child coroutines to complete
job1.join()
job2.join()

println("All tasks completed successfully")
}
} catch (e: Exception) {
println("An error occurred: ${e.message}")
}
}


خروجی کد بالا به صورت زیر است :

Task 1 completed
Task 2 completed

All tasks completed successfully

در این کد، دو کوروتین (job1 و job2) در یک supervisorScope اجرا می‌شوند. ابتدا کوروتین job1 با یک تأخیر 1000 میلی‌ثانیه اجرا شده و پس از آن پیام "Task 1 completed" را چاپ می‌کند. سپس کوروتین job2 با یک تأخیر 2000 میلی‌ثانیه اجرا شده و پیام "Task 2 completed" را چاپ می‌کند. اما سپس یک استثناء (Exception) در job2 پرتاب می‌شود با پیام "Error in task 2".

با استفاده از supervisorScope، خطاهای مربوط به کوروتین‌های فرزند به کوروتین والد منتقل نمی‌شوند. بنابراین، بلاک catch در main فراخوانی نخواهد شد و برنامه به اجرای خود ادامه می‌دهد.

خب حالا ببینیم SupervisorJob چی هست !!

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

  1. حالا SupervisorJob یک زیرکلاس از Job است که برای ایجاد یک محیط کاری (supervisor scope) استفاده می‌شود که کوروتین‌های مستقل از یکدیگر راه اندازی می‌شوند.
    در یک SupervisorJob، اگر یکی از کوروتین‌ها با خطا مواجه شود، فقط آن کوروتین لغو می‌شود و سایر کوروتین‌ها ادامه می‌یابند.

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


suspend fun main() {
try {
val parentJob = Job() // Creating a parent Job

val child1 = CoroutineScope(parentJob).launch {
delay(1000)
println("Task 1 completed")
}

val child2 = CoroutineScope(parentJob).launch {
delay(2000)
println("Task 2 completed")
throw Exception("Error in task 2")
}

// Wait for all child coroutines to complete
parentJob.join()

println("All tasks completed successfully")
} catch (e: Exception) {
println("An error occurred: ${e.message}")
}
}

در این مثال، هر دو کوروتین child1 و child2 به عنوان فرزندان یک Job ایجاد شده‌اند. بنابراین، اگر یکی از آنها با خطا مواجه شود، تمام کوروتین‌ها در parentJob لغو می‌شوند.هر دو کروتین یک تاخیر دارند و پس از اتمام تاخیر، پیامی را چاپ می‌کنند. با توجه به این که کروتین دوم یک استثناء را پرتاب می‌کند، برنامه خطا خواهد داشت و عبارت "An error occurred: Error in task 2" را چاپ می‌کند.

حالا همین کد را با استفاده از SupervisorJob اصلاح می‌کنیم:


suspend fun main() {
try {
val parentJob = SupervisorJob() // Creating a parent SupervisorJob

val child1 = CoroutineScope(parentJob).launch {
delay(1000)
println("Task 1 completed")
}

val child2 = CoroutineScope(parentJob).launch {
delay(2000)
println("Task 2 completed")
throw Exception("Error in task 2")
}

// Wait for all child coroutines to complete
parentJob.join()

println("All tasks completed successfully")
} catch (e: Exception) {
println("An error occurred: ${e.message}")
}
}

در این‌جا از SupervisorJob به جای Job برای ایجاد والد استفاده شده است.وظیفه SupervisorJob تفاوتی با Job دارد. وقتی که یک کروتین کودک در یک SupervisorJob شکست می‌خورد (مثل پرتاب یک استثناء)، تنها کروتین متاثر می‌شود و کروتین‌های دیگر تحت مدیریت همچنان ادامه می‌یابند. بنابراین، در اینجا کروتین دوم یک استثناء را پرتاب می‌کند، اما کروتین اول همچنان ادامه دارد. پس از اتمام هر دو کروتین، پیام "All tasks completed successfully" چاپ می‌شود.

بنابراین، خروجی برنامه به صورت زیر خواهد بود:

Task 1 completed
Task 2 completed
All tasks completed successfully









coroutineslaunch delayکوروتین job
Android Developer
شاید از این پست‌ها خوشتان بیاید