CoroutineScope — این چیست، محدوده زندگی و کار در کوروتین‌ها

نویسنده: IT Sectr منتشر شده: 2026-06-22 زمان مطالعه: 10 دقیقه

CoroutineScope — اینترفیس Kotlin است که محدوده زندگی کوروتین را تعریف می‌کند و زمینه‌ای برای راه‌اندازی کوروتین‌های جدید فراهم می‌کند. به گفته مستندات Kotlin، 2025، هر نمونه CoroutineScope حاوی CoroutineContext است و تمام کوروتین‌های راه‌اندازی شده در آن را مدیریت می‌کند. وقتی scope پایان می‌یابد (cancel)، همه کوروتین‌های فرزند به طور خودکار لغو می‌شوند که از نشت حافظه جلوگیری می‌کند.

نکات اصلی

  • CoroutineScope — اینترفیس با یک فیلد CoroutineContext که چرخه زندگی کوروتین‌ها را تعریف می‌کند
  • Job — عنصر زمینه مسئول لغو: لغو scope همه کوروتین‌های فرزند را لغو می‌کند
  • رقابت ساختاری — اصلی که بر اساس آن کوروتین‌های فرزند به scope والد متصل هستند
  • GlobalScope — scope برای کل برنامه که به دلیل خطر نشت توصیه نمی‌شود
  • supervisorScope — scope ویژه که در آن لغو یک کوروتین فرزند بقیه را لغو نمی‌کند

CoroutineScope در Kotlin چیست؟

CoroutineScope — اینترفیس بنیادی از کتابخانه kotlinx.coroutines است که به عنوان ظرفی برای کوروتین‌ها عمل می‌کند. این مرزهای زندگی کوروتین‌ها را تعریف می‌کند: وقتی scope پایان می‌یابد، همه کوروتین‌های داخل آن به طور خودکار لغو می‌شوند.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

اینترفیس فقط یک فیلد دارد — coroutineContext. از طریق آن، scope توزیع‌کننده (Dispatcher)، وظیفه (Job)، مدیریت استثنا و سایر عناصر زمینه را برای همه کوروتین‌های راه‌اندازی شده در خود فراهم می‌کند.

نقش در کتابخانه kotlinx.coroutines

همه توابع راه‌اندازی کوروتین — launch، async، runBlocking — توابع توسعه‌دهنده روی CoroutineScope هستند. این بدان معناست که فقط در صورت وجود شیء scope می‌توان آنها را فراخوانی کرد. چنین طراحی تضمین می‌کند که هر کوروتین والد و چرخه زندگی مشخصی دارد.

CoroutineScope در کجا استفاده می‌شود

در Android هر مؤلفه معماری scope خود را دارد: viewModelScope برای ViewModel، lifecycleScope برای Activity/Fragment. در برنامه‌های سرور، scope می‌تواند به درخواست HTTP یا به استخر اتصالات پایگاه داده متصل شود.

CoroutineScope چگونه کار می‌کند: Job و رقابت ساختاری

درک ساختار داخلی CoroutineScope نیازمند آشنایی با مفهوم Job و اصل رقابت ساختاری است.

Job — وظیفه کوروتین

هر کوروتین پس از راه‌اندازی یک شیء Job (یا Deferred برای async) برمی‌گرداند. Job یک وظیفه با چرخه زندگی مشخص را نشان می‌دهد: New، Active، Completing، Completed، Cancelling، Cancelled. اشیاء Job ساختار درختی تشکیل می‌دهند:

  • Job والد — scope که کوروتین در آن راه‌اندازی شده است
  • Job فرزند — هر کوروتین راه‌اندازی شده از طریق launch/async
  • لغو والد → لغو همه فرزندان
  • استثنا در فرزند → لغو والد (به جز supervisorScope)

اصل رقابت ساختاری

رقابت ساختاری — اصل معماری کلیدی Kotlin Coroutines که در آن طول عمر کوروتین به طول عمر scope آن متصل است. این در تضاد با مدل «fire-and-forget» است که در آن کوروتین پس از پایان scope به زندگی ادامه می‌دهد. مزایای رقابت ساختاری:

  • چرخه زندگی قابل پیش‌بینی — وقتی scope پایان می‌یابد، همه کوروتین‌ها تضمیناً متوقف می‌شوند
  • مدیریت خودکار خطا — استثنا در هر کوروتین فرزند به scope منتشر می‌شود
  • بدون نشت — هیچ کوروتینی پس از پایان scope فعال نمی‌ماند
  • سلسله‌مراتب واضح — کد ساختار منطقی عملیات موازی را منعکس می‌کند

چرخه زندگی CoroutineScope

وقتی scope.cancel() فراخوانی می‌شود، Job scope به حالت Cancelled می‌رود که به صورت بازگشتی همه Jobهای فرزند را لغو می‌کند. پس از لغو، scope فقط با ایجاد یک نمونه جدید CoroutineScope قابل استفاده مجدد است.

ایجاد و پیکربندی CoroutineScope

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

تابع کارخانه‌ای CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("در حال اجرا روی ${Thread.currentThread().name}")
}

تابع کارخانه‌ای CoroutineContext را می‌پذیرد و scope را با زمینه مشخص شده ایجاد می‌کند. در مثال از Dispatchers.Default برای وظایف CPU-intensive و SupervisorJob که استثناها را بین کوروتین‌های فرزند ایزوله می‌کند استفاده شده است.

پیاده‌سازی اینترفیس از طریق ترکیب

kotlin
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 تفویض شود:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // کوروتین در scope DataLoader اجرا می‌شود
        }
    }
}

این رویکرد زمانی راحت است که کلاس خودش scope است و می‌خواهد روش‌های راه‌اندازی کوروتین را فراهم کند. با این حال مراقب باشید: کلاس همه روش‌های CoroutineScope از جمله cancel را به ارث می‌برد که ممکن است کپسوله‌سازی را نقض کند.

GlobalScope در مقابل CoroutineScope سفارشی

GlobalScope — یک نمونه منحصربه‌فرد CoroutineScope برای کل برنامه است. استفاده از آن در کد تولید به طور رسمی توصیه نمی‌شود.

مشکلات GlobalScope

  • عدم رقابت ساختاری — کوروتین‌ها در GlobalScope به چرخه زندگی مؤلفه متصل نیستند
  • نشت حافظه — کوروتین ممکن است پس از بسته شدن Activity/Fragment به اجرا ادامه دهد
  • تست دشوار — GlobalScope را نمی‌توان در تست‌ها جایگزین کرد
  • مصرف غیرقابل کنترل منابع — بسیاری از کوروتین‌ها ممکن است طولانی‌تر از حد انتظار کار کنند

چه زمانی GlobalScope موجه است

JetBrains استفاده از GlobalScope را فقط در سناریوهای نادر مجاز می‌داند: فرآیندهای پس‌زمینه در سطح برنامه که باید حتی پس از بسته شدن همه Activityها زنده بمانند (مثلاً همگام‌سازی داده‌ها، تحلیل). اما حتی در این موارد بهتر است scope خود را با CoroutineScope(SupervisorJob()) ایجاد کنید.

توصیه

همیشه از CoroutineScope سفارشی با مدیریت صریح چرخه زندگی استفاده کنید. در Android اینها viewModelScope و lifecycleScope هستند. در برنامه‌های سرور برای هر درخواست یا استخر اتصالات scope ایجاد کنید.

coroutineScope در مقابل supervisorScope: تفاوت چیست

هر دو تابع suspend هستند که scope موقتی برای وظایف موازی ایجاد می‌کنند، اما رفتار آنها در هنگام استثناها اساساً متفاوت است.

ویژگیcoroutineScopesupervisorScope
رفتار در هنگام خطااستثنا در کوروتین فرزند همه بقیه را لغو می‌کنداستثنا در کوروتین فرزند بقیه را لغو نمی‌کند
انتشار خطابله، اولین استثنا به بیرون منتشر می‌شودبله، اولین استثنا به بیرون منتشر می‌شود
Job پیش‌فرضJob() — فرزندان به والد متصل هستندSupervisorJob() — فرزندان به یکدیگر وابسته نیستند
موارد استفاده معمولعملیات اتمی از چند مرحلهوظایف موازی مستقل (بارگیری‌های UI)

چه زمانی coroutineScope را انتخاب کنیم

زمانی از coroutineScope استفاده کنید که چند عملیات موازی یک عملیات اتمی واحد را تشکیل می‌دهند. مثلاً بارگیری داده‌ها از سه سرور: اگر یک درخواست شکست بخورد، بقیه بی‌معنی هستند.

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

اگر getProduct یا getReviews استثنا抛出 کنند — هر دو کوروتین لغو می‌شوند و استثنا به کد فراخوان منتشر می‌شود.

چه زمانی supervisorScope را انتخاب کنیم

زمانی از supervisorScope استفاده کنید که عملیات موازی به یکدیگر وابسته نیستند. مثلاً بارگیری داده‌های پروفایل در چند بخش مستقل: اگر بخش توصیه‌ها شکست بخورد، هدر پروفایل و لیست دوستان باید نمایش داده شوند.

اشتباهات رایج هنگام کار با CoroutineScope

متداول‌ترین اشتباهات توسعه‌دهندگان هنگام استفاده از CoroutineScope در Kotlin را بررسی می‌کنیم.

اشتباه 1: فراموش کردن لغو scope

متداول‌ترین سناریوی نشت کوروتین — ایجاد scope بدون فراخوانی cancel هنگام پایان مؤلفه. اگر scope لغو نشود، کوروتین‌ها با نگه داشتن ارجاعات به اشیاء به کار ادامه می‌دهند. در Android از viewModelScope یا lifecycleScope استفاده کنید که به طور خودکار لغو می‌شوند.

اشتباه 2: استفاده از GlobalScope در Activity یا Fragment

GlobalScope چرخه زندگی مؤلفه‌های Android را نادیده می‌گیرد. کوروتین راه‌اندازی شده در GlobalScope پس از بسته شدن Activity به اجرا ادامه می‌دهد و سعی در به‌روزرسانی UI می‌کند — که منجر به crash می‌شود. همیشه برای مؤلفه‌های UI از lifecycleScope استفاده کنید.

اشتباه 3: استفاده مجدد از scope لغو شده

پس از فراخوانی cancel() نمی‌توان از scope مجدداً استفاده کرد — همه کوروتین‌های داخل آن قبلاً تمام شده‌اند. یک نمونه جدید CoroutineScope از طریق تابع کارخانه‌ای ایجاد کنید. Job() از فعال‌سازی مجدد پشتیبانی نمی‌کند.

اشتباه 4: تفویض نادرست اینترفیس CoroutineScope

در تفویض با by، کلاس متد عمومی cancel() را دریافت می‌کند که می‌تواند از هر جایی فراخوانی شود و کپسوله‌سازی را نقض کند. scope را به عنوان فیلد خصوصی نگه دارید، نه تفویض اینترفیس.

سؤالات متداول

تفاوت CoroutineScope با CoroutineContext چیست؟

CoroutineScope — اینترفیسی است که CoroutineContext را در اختیار دارد و مسئول چرخه زندگی کوروتین‌ها است. CoroutineContext — مجموعه‌ای از عناصر (توزیع‌کننده، job، مدیریت خطا) است که تعیین می‌کند کوروتین «چگونه» اجرا شود. یکی از تفاوت‌ها: scope کوروتین‌ها را ایجاد می‌کند، context رفتار آنها را مدیریت می‌کند.

آیا می‌توان CoroutineScope با SupervisorJob ایجاد کرد؟

بله، این یک الگوی استاندارد است: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob از لغو آبشاری کوروتین‌های فرزند هنگام استثنا در یکی از آنها جلوگیری می‌کند. این برای وظایف موازی مستقل مفید است که در آن خطا در یکی نباید دیگران را متوقف کند.

CoroutineScope چند کوروتین می‌تواند داشته باشد؟

محدودیتی برای تعداد کوروتین‌ها در scope وجود ندارد — آنها فقط با حافظه موجود و تنظیمات توزیع‌کننده محدود می‌شوند. محدودیت عملی معمولاً هزاران کوروتین فعال در یک scope است. با این حال تعداد زیاد کوروتین ممکن است نشان‌دهنده مشکلات معماری باشد.

چگونه کد با CoroutineScope را تست کنیم؟

روش صحیح ارسال scope به کلاس از طریق سازنده یا استفاده از runBlockingTest / runTest از kotlinx-coroutines-test است. در تست‌ها می‌توان scope را با TestCoroutineDispatcher جایگزین کرد و اجرای کوروتین‌ها را به صورت دستی کنترل نمود.

آیا یک کوروتین می‌تواند scope خود را داشته باشد؟

خیر، scope یک ظرف خارجی برای کوروتین است. خود کوروتین scope نیست. با این حال در داخل کوروتین می‌توان از طریق coroutineScope یا supervisorScope برای راه‌اندازی موازی کوروتین‌های فرزند scope جدیدی ایجاد کرد.

خلاصه

  • CoroutineScope — اینترفیس با فیلد coroutineContext که چرخه زندگی کوروتین‌های راه‌اندازی شده در آن را تعریف می‌کند
  • رقابت ساختاری — لغو scope به طور خودکار همه کوروتین‌های فرزند را لغو می‌کند و از نشت حافظه جلوگیری می‌کند
  • Job و SupervisorJob — دو حالت مدیریت خطا: لغو آبشاری (Job) و خطاهای ایزوله (SupervisorJob)
  • GlobalScope — به دلیل عدم اتصال به چرخه زندگی برای تولید توصیه نمی‌شود
  • coroutineScope در مقابل supervisorScope — عملیات موازی اتمی در مقابل وظایف موازی مستقل
  • viewModelScope و lifecycleScope — scopeهای آماده برای Android که به طور خودکار هنگام پایان مؤلفه لغو می‌شوند
  • تابع کارخانه‌ای — روش ترجیحی ایجاد scope از طریق CoroutineContext + فراخوانی صریح cancel

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

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید