Coroutine در کاتلین – بخش دوم: تابع های تعلیقی
در بخش قبلی این راهنما با مفاهیم مقدماتی کوروتینها در زبان کاتلین آشنا شدیم. در این بخش بر روی مفهوم «تابع های تعلیقی» (Suspending Functions) متمرکز میشویم. برای مطالعه بخش قبلی روی لینک زیر کلیک کنید:



در بخش قبلی اشاره کردیم که تابعهای تعلیقی به ما امکان میدهند که یک تابع را تعلیق کنیم و سپس آن را از سر بگیریم. با توجه به این که مطالب مربوط به تابعهای تعلیقی نیازمند مقاله جداگانهای بود، در بخش قبلی توضیح بیشتری در مورد آنها ارائه نکردیم. در این مطلب به تفصیل به توضیح آنها میپردازیم.
منظور از تابع تعلیقی چیست؟
تابع تعلیقی را میتوان مانند یک تابع معمولی در نظر گرفت که میتواند مکث یابد و پس از اجرای وظیفهاش از سر گرفته شود. یعنی میتوانیم یک وظیفه زمانگیر را در تابع قرار دهیم و منتظر بمانیم تا کامل شود. به همین دلیل است که کوروتینها را باید به روش ترتیبی بدون استفاده از callback یا RxJava بنویسیم.

تابعهای تعلیقی میتوانند صرفاً از یک کوروتین مورد استفاده قرار گیرند. یک تابع تعلیق یافته میتواند به صورت یک تابع معمولی مورد استفاده قرار گیرد و اجرای کوروتین را تعلیق خواهد کرد. برای نمونه delay() یک تابع تعلیقی داخلی است. به لطف یادآوری اندروید استودیو با توجه به آیکون فلش در سمت چپ پنل متوجه میشویم که این یک تابع تعلیقی است. زمانی که delay(1_000) را در کوروتین فرا میخوانیم، اجرا را به مدت 1 ثانیه بدون مسدودسازی نخ تعلیق میکند و سپس به کوروتین بازمیگردد تا تابع doSomething() را اجرا کند.
برای این که یک تابع تعلیقی را خودمان تعریف کنیم باید از مادیفایر suspend بهره بگیریم. آیا کافی است suspend را به تابع معمول خود اضافه کنیم تا وظایف سنگین را که نخ را مسدود میسازند به تابع غیر مسدودکننده تبدیل کنیم؟ پاسخ منفی است. با این که مستندات رسمی اشاره میکنند: «تابع تعلیقی میتواند اجرای کد را بدون مسدود ساختن نخ جاری و با فراخوانی تابعهای تعلیقی دیگر معلق سازد.» اما همچنان باید به Dispatchers که تابعهای تعلیقی را با استفاده از آن اجرا میکنیم توجه داشته باشیم.
اگر suspend را روی یک تابع نرمال قرار دادهاید در این صورت IDE هشداری به صورت استفاده مکرر از مادیفایر suspend ارائه میکند.
سادهترین و درستترین روش این است که وظیفه مورد نظر را درون یک ()withContext قرار دهیم و dispatcher-های مورد نیاز را تعریف کنیم. برای نمونه اگر وظیفه سنگینی با محاسبات مرتبط است باید آن را درون withContext(Dispatchers.default) قرار دهیم.
برای استفاده از تابعهای تعلیقی چندین روش وجود دارد که در ادامه آنها را توضیح خواهیم داد.
فراخوانی تابعهای مسدودکننده از تابعهای تعلیقی
قرار دادن وظایف زمانبر در یک تابع تعلیقی ایده مناسبی محسوب میشود. برای نمونه وظیفه مرتبط با شبکه مانند واکشی دادههای کاربر و بهروزرسانی UI یک کار متداول است که معمولاً باید انجام دهیم. بزرگترین مشکل این است که این نوع از وظایف سنگین موجب مسدود شدن نخ اصلی اندروید میشوند. برای جلوگیری از توقف اپلیکیشن (ANR) چنین وظایفی را در نخ پسزمینه قرار میدهیم. مشکل بعدی این است که بهروزرسانی UI در نخ پسزمینه مجاز نیست و از این رو باید از Activity.runOnUiThread(Runnable) و یا حتی Handler به این منظور بهره بگیریم.
به نظر میرسد که انجام این کار برای توسعهدهندگان اندروید به این روش چندان آسان نباشد. خوشبختانه کوروتینهای کاتلین در اینجا به کمک ما میآیند:
قطعه کد فوق پس از این که دادههای کاربر واکشی شدند، UI را بهروزرسانی میکند. به علاوه وظیفه شبکه موجب مسدود شدن نخ اصلی نمیشود و در نخ کارگر اجرا میشود، زیرا با استفاده از دستور withContext(Dispatchers.IO) نخ را عوض کردهایم.
Callback و SuspendCancellableCoroutine
فرض کنید از قبل یک پروژه آنلاین اندروید داریم. در این پروژه از وظایف ناهمگام زیادی برای انتظار جهت خواندن پایگاه داده یا واکشی کردن دادهها از سرور استفاده شده است.
استفاده از تابعهای Callback یک روش احتمالی برای مدیریت دادهها روی نخ اصلی است. بنابراین سؤال این است که چگونه میتوان این تابعهای Callback را به کوروتینها تبدیل کرد؟ در اینجا باید از SuspendCancellableCoroutine استفاده کنیم.
SuspendCancellableCoroutine یک CancellableContinuation بازگشت میدهد که از resume, resumeWithException استفاده میکند و در صورتی که اجرا لغو شود یک استثنای CancellationException صادر میکند. تابع مشابه دیگری به نام suspendCoroutine نیز وجود دارد که تنها تفاوت آن این است که suspendCoroutine نمیتواند از سوی ()Job.cancel لغو شود.
CancellableContinuation
ما میتوانیم یک بلوک را در suspendCancellableCoroutine با استفاده از آرگومان CancellableContinuation اجرا کنیم. سه روش برای استفاده از CancellableContinuation وجود دارند:
1. resume(value: T)
این دستور اجرای کوروتین متناظر را با ارسال مقدار بازگشتی آخرین نقطه تعلیق از سر میگیرد.
در نمونه کد فوق اگر CancellableContinuation.resume(user) را فراخوانی کنیم، تابع fetchUser() مقدار [user] را به val user بازمیگرداند.
2. resumeWithException(exception: Throwable)
این دستور اجرای کوروتین متناظر را از سر میگیرد به طوری که [exception] درست پس از آخرین نقطه تعلیق مجدداً صادر میشود:
در کد نمونه فوق زمانی که CancellableContinuation.resumeWithException(user) را فراخوانی میکنیم، تابع ()fetchUser اقدام به صدور [exception] میکند. بدین ترتیب updateUser(user) نمیتواند فراخوانی شود و به جای آن try-catch به مدیریت استثنا میپردازد. سپس کد پس از بلوک try-catch به صورت پیوسته اجرا میشود.
3. cancellableContinuation.cancel()
با این که «استثناهای بررسی شده» (checked exceptions) ندارد، اما باید همه استثناهای ممکن را همچنان در try-catch مدیریت کنیم. در غیر این صورت اپلیکیشن از کار میافتد. اما یک استثنای خاص هست که CancellationException نام دارد و زمانی صادر میشود که ()cancellableContinuation.cancel را فراخوانی کنیم.
حتی زمانی که این استثنا را مدیریت نکنید، موجب بروز کرش میشود. اما کد زیر پس از آن اجرا نخواهد شد:
فراخوانی RxJava از تابعهای تعلیقی
در صورتی که از RxJava در پروژه خود استفاده کنیم یک کتابخانه به نام kotlinx-coroutines-rx2 وجود دارد که میتواند RxJava را به کوروتین تبدیل کند. آن را با کد زیر ایمپورت کنید:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.2"
در تصویر زیر همه سازندگان کوروتین را میبینید:

برای مثال اگر از Single در RxJava استفاده کنیم، ()Single.await به ما کمک میکند که RxJava را به suspendCancellableCoroutine تبدیل کنیم.

همچنان که در تصویر فوق میبینید، ()await حالت موفق را به ()cancellableContinuation.resume و حالت شکست را به ()cancellableContinuation.resumeWithException ارسال میکند. در ادامه اقدام به پیادهسازی کد دموی خود میکنیم:
لاگ خروجی به صورت زیر است:
D/demo: (1) fetchUserFromServer start, Thread[RxCachedThreadScheduler-1,5,main] D/demo: (2) fetchUserFromServer onSuccess, Thread[RxCachedThreadScheduler-1,5,main] D/demo: (3) updateUser, Thread[main,5,main]
کد ()fetchUserFromServer().await موجب تعلیق کوروتین میشود و تا زمانی که RxJava نتیجه را بازگشت دهد صبر میکند. حال سؤال این است که اگر Single در RxJava شکست بخورد و یک استثنا صادر شود چه میشود؟
در این حالت استثنا در try-catch مدیریت میشود. لاگ به صورت زیر است:
D/demo: (1) fetchUserFromServer start, Thread[RxCachedThreadScheduler-1,5,main]
D/demo: (2) fetchUserFromServer onError, Thread[RxCachedThreadScheduler-1,5,main]
D/demo: (4) {java.io.IOException}, Thread[main,5,main]
همانند Maybe ،bservable در RxJava تابعهای اکستنشن متناظری برای استفاده در این حالت وجود دارند. فهرست آنها به شرح زیر است:

سخن پایانی
به این ترتیب به پایان این مقاله میرسیم، امیدواریم مطالعه این راهنما به افزایش دانش شما در مورد تابعهای تعلیقی کمک کرده باشد و بدین ترتیب بتوانید آنها را در پروژههای خود پیادهسازی کنید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی زبان برنامه نویسی کاتلین (Kotlin) برای توسعه اندروید (Android)
- مجموعه آموزشهای برنامهنویسی اندروید
- زبان برنامه نویسی کاتلین (Kotlin) — راهنمای کاربردی
- آشنایی با مفهوم سازنده (Constructor) در کاتلین (Kotlin) — به زبان ساده
==












