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

شیرجه ای عمیق تر در مفاهیم Coroutine ها

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

به طور کلی Coroutines از ترکیب Co + Routines تشکیل شده است که مخفف کلمات cooperation و Routines میباشد.به معنای همکاری توابع میباشد.خب کوروتین ها به همین صورت کار میکنند.

به این معنی که وقتی توابع با یکدیگر همکاری می کنند، آن را Coroutine می نامیم.

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

حالا قطعه کد زیر را در نظر بگیرید:

fun functionA(case: Int) {
when (case) {
1 -> {
taskA1()
functionB(1)
}
2 -> {
taskA2()
functionB(2)
}
3 -> {
taskA3()
functionB(3)
}
4 -> {
taskA4()
functionB(4)
}
}
}

و حالا کد زیر را ببینید:

fun functionB(case: Int) {
when (case) {
1 -> {
taskB1()
functionA(2)
}
2 -> {
taskB2()
functionA(3)
}
3 -> {
taskB3()
functionA(4)
}
4 -> {
taskB4()
}
}
}

حالا اگه فراخوانی تایع A را به صورت زیر داشته باشیم :

functionA(1)

وقتی یک تابع Bرا از تابع A فراخوانی می کنید، Aباید منتظر بماند تا B به پایان برسد تا اجرا ادامه یابد، این همان چیزی است که ما توابع غیر همکار می نامیم. و این کنترل تابع Bبر روی A توسط سیستم عامل انجام می شود، B باید به پایان برسد، بنابراین سیستم عامل می تواند کنترل را به Aبرگرداند. برای اینکه A در این وضعیت نیاز به انتظار B نداشته باشد، باید Bرا در یک ترد دیگر اجرا کنید و اکنون Aنیازی به صبر ندارد.

با Coroutines، همکاری فوق را می توان به راحتی انجام داد که بدون استفاده از when (case) است که در مثال بالا برای درک بهتر از آن استفاده کردم. اکنون متوجه شدیم که وقتی صحبت از همکاری بین توابع به میان می‌آید، منظورمون چیه.

پس در نتیجه کوروتین ها توابعی هستند که با هم همکاری دارند، توابعی که در آن کنترل نحوه اجراها توسط «کاربر» و نه سیستم عامل انجام می شود، بنابراین می توانید همین کار را با استفاده از تردهای مختلف یا حتی یک ترد داشته باشید. و برای داشتن این اثر، کنترل از یک تابع به تابع دیگر منتقل می شود به گونه ای که نقطه خروج از تابع اول و نقطه ورود به تابع دوم به خاطر سپرده می شود. به این ترتیب، می‌توانید این احساس را داشته باشید که وقتی واقعاً از یک ترد استفاده می‌کنید، به ظاهر از چند ترد استفاده می‌کنید و به همین دلیل اغلب می‌شنوید که کوروتین‌ها تردهای سبک هستند.

خب این توابعی که با کوروتین ها با هم همکاری میکنند از نوع suspend هستند.مادیفایر Suspend به کامپایلر اعلام میکند که این تابع میتواند به حالت تعلیق در بیاد(suspend شود) و مجدد از همان نقطه ای که به حالت تعلیق رفته بود از سر گرفته شود(resume شود).این نقطه را کامپایلر به خاطر خواهد سپرد.

پس با مفهوم suspend Function ها هم آشنا شدیم.

حالا قطعه کدهای زیر را در نظر بگیرید که بیشتر مطالب بالا را درک کینم.

suspend fun makeLogin(login: String, password: String, callback: (Token) -> Unit) { // request login callback(token) } suspend fun loadMovies(callback: (List<Movie>) -> Unit) { makeLogin(&quotsomeValue&quot, &quotsomeValue&quot) { token -> // request movies with token callback(movies) } } fun someFunction() { loadMovies { movies -> printMovies(movies) } }

خب در این کد کامپایلر از تابع someFunction خطا میگیرد.چرا؟چون که توابع suspend فقط باید از توابع suspend دیگر صدا زده شوند یا از درون کوروتین ها.

پس کد را به صورت زیر اصلاح میکنیم که خطا برطرف شود

suspend fun makeLogin(login: String, password: String) : Token {
// request login
return token
}
suspend fun loadMovies(token: Token) : List<Movie> {
val token = makeLogin("someValue", "someValue")
// request movies with token
return movies
}
fun someFunction() {
GlobalScope.launch {
val token = makeLogin("someValue", "someValue")
val movies = loadMovies(token)
printMovies(movies)
}
}

خب حالا دیگه کامپایلر از کد ما خطا نمیگیره چون ما توابع تعلیق خودمون رو داخل یک کوروتین کال کردیم.حالا میبینید که توابع ما چطوری به کمک کوروتین ها با هم همکاری دارند و دیگه حتی نیازی هم به کال بک ها نداریم و اونا رو حذف کردیم.

خب حالا اون GlobalScope , launch چیه؟تا اینجا بدون که launch یک کوروتین در GlobalScope ایجاد میکنه.در اینجا launch یک لامبدا یا یک تابع بی‌نام (anonymous function) از نوع suspend به عنوان ورودی میگرید.(همان کدی که درون بلاک launch است. (اجازه بده در پست بعدی کامل برات توضیح بدم)

خب بریم کد رو توضیح بدیم چطوری کار میکنه!! makeLogin و loadMovies توابع asyncهستند، ممکن است چند ثانیه طول بکشد تا تمام شوند، اما شبیه توابع غیر همگام هستند. هنگامی که makeLogin فراخوانی می شود، تابع launch به حالت تعلیق در می آید و زمانی که makeLogin تمام شد از سر گرفته می شود و با فراخوانی loadMovies همین اتفاق می افتد. اگر داخل loadMovies یک تابع suspendدیگری بود، تا زمانی که تابع دیگر تمام شود به حالت تعلیق در می آمد.

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

1. اگر توابع makeLogin و loadMovies به صورت همروند (concurrently) فراخوانی شوند، به این معناست که اجرای آنها ممکن است همزمان در حال اجرا در تردهای مختلف باشد. برای مثال، اگر در یک محیط کوروتینی، دو کوروتین مستقل ایجاد شود که هر کدام از این توابع را فراخوانی کنند، ممکن است هر کدام از این توابع در تردهای مختلفی اجرا شوند.

2. اگر از کوروتین‌های متفاوتی برای فراخوانی توابع استفاده شود، هر تابع ممکن است در تردی مجزا اجرا شود. برای مثال، اگر تابع makeLogin در یک کوروتین و تابع loadMovies در کوروتین دیگری فراخوانی شود، ممکن است هر کدام از این توابع در تردهای مختلفی اجرا شوند.

بنابراین، زمانی که توابعsuspend در کوروتین‌ها فراخوانی می‌شوند و برنامه اصلی همچنین در یک کوروتین اجرا می‌شود، می‌توان انتظار داشت که این توابع در تردهای مختلفی اجرا شوند، به ویژه اگر به صورت همروند فراخوانی شوند.

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

اجرای توابع suspend در یک ترد
اجرای توابع suspend در یک ترد


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

امیدوارم مفید براتون واقع شده باشد.

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

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