CoroutineScope — اینترفیس Kotlin است که محدوده زندگی کوروتین را تعریف میکند و زمینهای برای راهاندازی کوروتینهای جدید فراهم میکند. به گفته مستندات Kotlin، 2025، هر نمونه CoroutineScope حاوی CoroutineContext است و تمام کوروتینهای راهاندازی شده در آن را مدیریت میکند. وقتی scope پایان مییابد (cancel)، همه کوروتینهای فرزند به طور خودکار لغو میشوند که از نشت حافظه جلوگیری میکند.
نکات اصلی
CoroutineScope — اینترفیس بنیادی از کتابخانه kotlinx.coroutines است که به عنوان ظرفی برای کوروتینها عمل میکند. این مرزهای زندگی کوروتینها را تعریف میکند: وقتی scope پایان مییابد، همه کوروتینهای داخل آن به طور خودکار لغو میشوند.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
اینترفیس فقط یک فیلد دارد — coroutineContext. از طریق آن، scope توزیعکننده (Dispatcher)، وظیفه (Job)، مدیریت استثنا و سایر عناصر زمینه را برای همه کوروتینهای راهاندازی شده در خود فراهم میکند.
همه توابع راهاندازی کوروتین — launch، async، runBlocking — توابع توسعهدهنده روی CoroutineScope هستند. این بدان معناست که فقط در صورت وجود شیء scope میتوان آنها را فراخوانی کرد. چنین طراحی تضمین میکند که هر کوروتین والد و چرخه زندگی مشخصی دارد.
در Android هر مؤلفه معماری scope خود را دارد: viewModelScope برای ViewModel، lifecycleScope برای Activity/Fragment. در برنامههای سرور، scope میتواند به درخواست HTTP یا به استخر اتصالات پایگاه داده متصل شود.
درک ساختار داخلی CoroutineScope نیازمند آشنایی با مفهوم Job و اصل رقابت ساختاری است.
هر کوروتین پس از راهاندازی یک شیء Job (یا Deferred برای async) برمیگرداند. Job یک وظیفه با چرخه زندگی مشخص را نشان میدهد: New، Active، Completing، Completed، Cancelling، Cancelled. اشیاء Job ساختار درختی تشکیل میدهند:
رقابت ساختاری — اصل معماری کلیدی Kotlin Coroutines که در آن طول عمر کوروتین به طول عمر scope آن متصل است. این در تضاد با مدل «fire-and-forget» است که در آن کوروتین پس از پایان scope به زندگی ادامه میدهد. مزایای رقابت ساختاری:
وقتی scope.cancel() فراخوانی میشود، Job scope به حالت Cancelled میرود که به صورت بازگشتی همه Jobهای فرزند را لغو میکند. پس از لغو، scope فقط با ایجاد یک نمونه جدید CoroutineScope قابل استفاده مجدد است.
میتوان CoroutineScope را از طریق تابع کارخانهای یا از طریق پیادهسازی اینترفیس در کلاس خود ایجاد کرد. هر دو رویکرد را بررسی میکنیم.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("در حال اجرا روی ${Thread.currentThread().name}")
}
تابع کارخانهای CoroutineContext را میپذیرد و scope را با زمینه مشخص شده ایجاد میکند. در مثال از Dispatchers.Default برای وظایف CPU-intensive و SupervisorJob که استثناها را بین کوروتینهای فرزند ایزوله میکند استفاده شده است.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
scope را به عنوان فیلد کلاس ذخیره میکنیم و به صورت دستی cleanup را برای لغو آن فراخوانی میکنیم. این برای مؤلفههای با چرخه زندگی مدیریت شده مناسب است — برای مثال، مخازن یا مدیران.
Kotlin اجازه میدهد پیادهسازی CoroutineScope از طریق کلمه کلیدی by تفویض شود:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// کوروتین در scope DataLoader اجرا میشود
}
}
}
این رویکرد زمانی راحت است که کلاس خودش scope است و میخواهد روشهای راهاندازی کوروتین را فراهم کند. با این حال مراقب باشید: کلاس همه روشهای CoroutineScope از جمله cancel را به ارث میبرد که ممکن است کپسولهسازی را نقض کند.
GlobalScope — یک نمونه منحصربهفرد CoroutineScope برای کل برنامه است. استفاده از آن در کد تولید به طور رسمی توصیه نمیشود.
JetBrains استفاده از GlobalScope را فقط در سناریوهای نادر مجاز میداند: فرآیندهای پسزمینه در سطح برنامه که باید حتی پس از بسته شدن همه Activityها زنده بمانند (مثلاً همگامسازی دادهها، تحلیل). اما حتی در این موارد بهتر است scope خود را با CoroutineScope(SupervisorJob()) ایجاد کنید.
همیشه از CoroutineScope سفارشی با مدیریت صریح چرخه زندگی استفاده کنید. در Android اینها viewModelScope و lifecycleScope هستند. در برنامههای سرور برای هر درخواست یا استخر اتصالات scope ایجاد کنید.
هر دو تابع suspend هستند که scope موقتی برای وظایف موازی ایجاد میکنند، اما رفتار آنها در هنگام استثناها اساساً متفاوت است.
| ویژگی | coroutineScope | supervisorScope |
|---|---|---|
| رفتار در هنگام خطا | استثنا در کوروتین فرزند همه بقیه را لغو میکند | استثنا در کوروتین فرزند بقیه را لغو نمیکند |
| انتشار خطا | بله، اولین استثنا به بیرون منتشر میشود | بله، اولین استثنا به بیرون منتشر میشود |
| Job پیشفرض | Job() — فرزندان به والد متصل هستند | SupervisorJob() — فرزندان به یکدیگر وابسته نیستند |
| موارد استفاده معمول | عملیات اتمی از چند مرحله | وظایف موازی مستقل (بارگیریهای UI) |
زمانی از coroutineScope استفاده کنید که چند عملیات موازی یک عملیات اتمی واحد را تشکیل میدهند. مثلاً بارگیری دادهها از سه سرور: اگر یک درخواست شکست بخورد، بقیه بیمعنی هستند.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
اگر getProduct یا getReviews استثنا抛出 کنند — هر دو کوروتین لغو میشوند و استثنا به کد فراخوان منتشر میشود.
زمانی از supervisorScope استفاده کنید که عملیات موازی به یکدیگر وابسته نیستند. مثلاً بارگیری دادههای پروفایل در چند بخش مستقل: اگر بخش توصیهها شکست بخورد، هدر پروفایل و لیست دوستان باید نمایش داده شوند.
متداولترین اشتباهات توسعهدهندگان هنگام استفاده از CoroutineScope در Kotlin را بررسی میکنیم.
متداولترین سناریوی نشت کوروتین — ایجاد scope بدون فراخوانی cancel هنگام پایان مؤلفه. اگر scope لغو نشود، کوروتینها با نگه داشتن ارجاعات به اشیاء به کار ادامه میدهند. در Android از viewModelScope یا lifecycleScope استفاده کنید که به طور خودکار لغو میشوند.
GlobalScope چرخه زندگی مؤلفههای Android را نادیده میگیرد. کوروتین راهاندازی شده در GlobalScope پس از بسته شدن Activity به اجرا ادامه میدهد و سعی در بهروزرسانی UI میکند — که منجر به crash میشود. همیشه برای مؤلفههای UI از lifecycleScope استفاده کنید.
پس از فراخوانی cancel() نمیتوان از scope مجدداً استفاده کرد — همه کوروتینهای داخل آن قبلاً تمام شدهاند. یک نمونه جدید CoroutineScope از طریق تابع کارخانهای ایجاد کنید. Job() از فعالسازی مجدد پشتیبانی نمیکند.
در تفویض با by، کلاس متد عمومی cancel() را دریافت میکند که میتواند از هر جایی فراخوانی شود و کپسولهسازی را نقض کند. scope را به عنوان فیلد خصوصی نگه دارید، نه تفویض اینترفیس.
سؤالات متداول
CoroutineScope — اینترفیسی است که CoroutineContext را در اختیار دارد و مسئول چرخه زندگی کوروتینها است. CoroutineContext — مجموعهای از عناصر (توزیعکننده، job، مدیریت خطا) است که تعیین میکند کوروتین «چگونه» اجرا شود. یکی از تفاوتها: scope کوروتینها را ایجاد میکند، context رفتار آنها را مدیریت میکند.
بله، این یک الگوی استاندارد است: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob از لغو آبشاری کوروتینهای فرزند هنگام استثنا در یکی از آنها جلوگیری میکند. این برای وظایف موازی مستقل مفید است که در آن خطا در یکی نباید دیگران را متوقف کند.
محدودیتی برای تعداد کوروتینها در scope وجود ندارد — آنها فقط با حافظه موجود و تنظیمات توزیعکننده محدود میشوند. محدودیت عملی معمولاً هزاران کوروتین فعال در یک scope است. با این حال تعداد زیاد کوروتین ممکن است نشاندهنده مشکلات معماری باشد.
روش صحیح ارسال scope به کلاس از طریق سازنده یا استفاده از runBlockingTest / runTest از kotlinx-coroutines-test است. در تستها میتوان scope را با TestCoroutineDispatcher جایگزین کرد و اجرای کوروتینها را به صورت دستی کنترل نمود.
خیر، scope یک ظرف خارجی برای کوروتین است. خود کوروتین scope نیست. با این حال در داخل کوروتین میتوان از طریق coroutineScope یا supervisorScope برای راهاندازی موازی کوروتینهای فرزند scope جدیدی ایجاد کرد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید