runBlocking — Coroutine Builder در Kotlin است که رشته جاری را تا تکمیل کوروتین ارسال شده مسدود میکند. برخلاف launch و async، این یک تابع suspend نیست و میتواند از کد معمولی (مسدودکننده) فراخوانی شود. به گفته مستندات JetBrains، 2024، runBlocking به عنوان پلی بین دنیای同步 و ناهمگام عمل میکند و امکان اجرای کوروتینها از تابع main و تستها را فراهم میکند.
نکات اصلی
runBlocking — تابعی در Kotlin است که یک CoroutineScope جدید ایجاد کرده و کوروتین ارسال شده را اجرا میکند، رشته جاری را تا تکمیل کامل آن مسدود میکند. برخلاف تمام Coroutine Builderهای دیگر، runBlocking یک تابع suspend نیست و میتواند از کد同步 معمولی فراخوانی شود. امضای runBlocking یک CoroutineContext و یک بلوک suspend دریافت کرده و نتیجه از نوع T را برمیگرداند.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking یک event-loop جدید در رشته جاری راهاندازی میکند. وقتی کوروتین یک تابع suspend را فراخوانی میکند (مثلاً delay() یا await())، runBlocking رشته را مسدود کرده و سایر کوروتینهای برنامهریزی شده را در همان رشته تا ازسرگیری کوروتین معلق اجرا میکند. این مسدودسازی مشارکتی است — رشته بیکار نمیماند، بلکه سایر کوروتینها را پردازش میکند.
مکانیزم داخلی runBlocking بر پایه event-loop است: هنگام فراخوانی تابع suspend، runBlocking اجرای بلوک جاری را معلق کرده و سایر کوروتینها را از صف اجرا میکند. وقتی تابع suspend کامل شد، اجرا ازسر گرفته میشود. این چرخه تا زمانی که همه کوروتینها کامل شوند ادامه مییابد.
runBlocking از استخر تکرشتهای خود برای اجرای کوروتینها استفاده میکند. برخلاف Dispatchers.IO یا Default، runBlocking رشتهها را تغییر نمیدهد — همه کوروتینها را در رشته جاری پردازش میکند و اجرای آنها را میاناندازی میکند. این تنها builderای است که اجرا در همان رشته را تضمین میکند.
fun main() {
val threadName = Thread.currentThread().getName()
println("قبل از runBlocking روی $threadName")
val result = runBlocking {
println("داخل runBlocking روی ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("بعد از runBlocking: $result")
}
خروجی نشان میدهد که هر سه println روی یک رشته اجرا میشوند. runBlocking رشته را تغییر نمیدهد، بلکه با استفاده از event-loop چندوظیفگی مشارکتی را درون یک رشته سازماندهی میکند.
runBlocking در سه سناریو موجه است: نقطه ورود main() در برنامههای کنسولی، تستهای واحد توابع suspend و پل — فراخوانی کد suspend از کتابخانههای مبتنی بر callback یا مسدودکننده. در کد تولیدی اندروید استفاده در رشته اصلی قطعاً ممنوع است.
| سناریو | قابلیت استفاده | ریسکها |
|---|---|---|
| main() برنامه کنسولی | بله | ندارد — این نقطه ورود است، رشته UI را مسدود نمیکند |
| تستهای JUnit | بله | حداقل — تستها ذاتاً同步 هستند |
| Android UI-Thread | خیر | ANR، تأخیر، هنگ کردن رابط کاربری |
| Callback → Coroutine | بله، با احتیاط | مسدودسازی استخر رشتهها در عملیات طولانی |
برای تستهای اندروید به جای runBlocking از kotlinx-coroutines-test با TestDispatcher استفاده کنید. این امکان کنترل زمان، بازنشانی خودکار و ایزولهسازی تستها را فراهم میکند.
در بیشتر سناریوها runBlocking را میتوان و باید با جایگزینهای ناهمگام عوض کرد. برای اندروید اینها عبارتند از viewModelScope، lifecycleScope یا CoroutineScope با dispatcher مناسب. برای تستها — TestCoroutineDispatcher و runTest.
// بد: runBlocking روی رشته اصلی اندروید
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// خوب: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
جایگزین تستها runTest از kotlinx-coroutines-test است. این یک TestCoroutineScope با زمان مجازی ایجاد میکند و امکان تست تأخیرها بدون انتظار واقعی را فراهم میکند. این کار تستها را سریعتر و قطعی میکند.
متداولترین سناریو — تست توابع suspend است. runBlocking در تستها امکان انتظار同步 برای نتیجه کوروتین بدون تغییر معماری را فراهم میکند. سناریوی دوم — کتابخانههایی با callback API، جایی که توابع suspend از زمینه مسدودکننده از طریق runBlocking فراخوانی میشوند.
// تست تابع suspend با runBlocking
class RepositoryTest {
@Test
fun `fetchUser returns correct data`() {
val repository = UserRepository(FakeApi())
val result = runBlocking {
repository.fetchUser("123")
}
assertEquals("John", result.name)
assertEquals("john@test.com", result.email)
}
}
برای پل زدن بین دنیای callback و suspend به جای callbackها از CompletableDeferred در ترکیب با runBlocking استفاده کنید — این کار زنجیره عملیات ناهمگام را ساده کرده و خوانایی کد را افزایش میدهد.
استفاده نادرست از runBlocking یکی از اشتباهات رایج هنگام انتقال از رویکرد مسدودکننده به کوروتینهاست. مشکلات اصلی: فراخوانی در Main-Thread اندروید، تودرتو کردن runBlocking، استفاده درون توابع ناهمگام و اجرای عملیات طولانی از طریق runBlocking.
قاعده طلایی: runBlocking یک پل است، نه جایگزین. فقط برای اتصال دنیای مسدودکننده و غیرمسدودکننده از آن استفاده کنید. برای همه وظایف دیگر از launch، async یا lifecycleScope استفاده کنید.
سؤالات متداول
runBlocking تنها builderای است که تابع suspend نیست. این یک event-loop در رشته جاری راهاندازی میکند و تا تکمیل همه کوروتینها کنترل را برنمیگرداند. launch و async بلافاصله کنترل را برمیگردانند و کوروتین را در پسزمینه اجرا میکنند.
توصیه نمیشود. ViewModel دارای viewModelScope داخلی است که به طور خودکار کوروتینها را مدیریت کرده و هنگام نابودی لغو میکند. runBlocking در ViewModel رشته را مسدود کرده و به لغو lifecycle واکنش نشان نمیدهد.
از runTest از کتابخانه kotlinx-coroutines-test استفاده کنید. این یک TestCoroutineScope با کنترل زمان مجازی، لغو خودکار و اجرای قطعی فراهم میکند.
Event-loop — چرخه پردازش رویدادها درون runBlocking. وقتی کوروتین معلق میشود (مثلاً delay())، event-loop به اجرای سایر کوروتینهای آماده در همان رشته سوئیچ میکند. این توهم چندوظیفگی را بدون تغییر رشته ایجاد میکند.
runBlocking تودرتو در یک رشته deadlock ایجاد میکند — بلوک بیرونی منتظر داخلی است، اما داخلی نمیتواند تا پایان بلوک بیرونی شروع شود. در رشتههای مختلف این مجاز است، اما به دلیل دشواری اشکالزدایی به شدت توصیه نمیشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید