Suspend function — تابعی با اصلاحکننده suspend است که میتواند اجرای خود را بدون مسدود کردن رشته متوقف کرده و بعداً در همان کوروتین ادامه دهد. بر اساس JetBrains Kotlin Docs, 2025، توابع suspend بلوک ساختمانی بنیادی کوروتینها هستند و ناهمزمانی را بدون callback فراهم میکنند. هر تابع suspend به یک ماشین حالت مبتنی بر Continuation کامپایل میشود که امکان مدیریت مؤثر نقاط توقف را فراهم میکند.
نکات اصلی
Suspend function — تابعی است که با کلمه کلیدی suspend اعلان شده و میتواند اجرا را در یک یا چند نقطه بدون مسدود کردن رشته متوقف کند. هر فراخوانی تابع suspend در داخل تابع suspend دیگر یک نقطه توقف بالقوه است.
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
کامپایلر Kotlin چنین تابعی را به ماشین حالت تبدیل میکند. هر نقطه توقف (فراخوانی تابع suspend دیگر) یک حالت (label) میشود. رشته فعلی بین حالتها آزاد میشود و پس از اتمام عملیات مورد انتظار، اجرا از حالت بعدی ادامه مییابد.
توابع suspend در Kotlin 1.3 (سال 2018) همراه با کوروتینها به عنوان یک ویژگی آزمایشی ظاهر شدند و در Kotlin 1.5 (سال 2021) پایدار شدند. قبل از آن، ناهمزمانی در Kotlin/Java از طریق callbackها، RxJava و CompletableFuture تأمین میشد. توابع suspend جایگزینی با نحو خطی و مدیریت خودکار رشتهها ارائه دادند.
درک ساختار داخلی توابع suspend کلید کار صحیح با کوروتینهاست. برخلاف توابع معمولی، هر تابع suspend به کلاسی با رابط Continuation کامپایل میشود.
کامپایلر Kotlin یک پارامتر از نوع Continuation به انتهای هر پارامتر تابع suspend اضافه میکند. Continuation شامل موارد زیر است:
فرض کنید یک تابع suspend با دو فراخوانی به توابع suspend دیگر داریم:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
کامپایلر آن را به ماشین حالت با برچسبها تبدیل میکند:
// نمایش سادهشده کد تولیدشده
fun process(cont: Continuation<Unit>): Any? {
val cont = cont as ProcessContinuation
when (cont.label) {
0 -> {
cont.label = 1
if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
cont.label = 2
val a = cont.result as TypeA
if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
2 -> {
println(cont.result)
Unit
}
}
}
مشاهده کلیدی: اگر تابع COROUTINE_SUSPENDED برگرداند، رشته فعلی آزاد میشود. وقتی عملیات ناهمزمان کامل شد، Continuation.resumeWith فراخوانی میشود و ماشین حالت از label بعدی ادامه میدهد.
اعلان تابع suspend تفاوتی با تابع معمولی ندارد، به جز کلمه کلیدی suspend قبل از fun. فقط یک محدودیت وجود دارد: تابع suspend را فقط میتوان از کوروتین یا تابع suspend دیگر فراخوانی کرد.
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
در این مثال delay نیز یک تابع suspend است که کوروتین را برای تعداد میلیثانیه مشخصی بدون مسدود کردن رشته متوقف میکند. پس از تأخیر، اجرا ازسر گرفته میشود.
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking پلی بین دنیای معمولی و کوروتینها ایجاد میکند. در داخل لامبدا، فراخوانی هر تابع suspend مجاز است.
Kotlin از نسخههای suspend انواع تابعی پشتیبانی میکند — suspend () -> T و suspend (A) -> B. این امکان را میدهد که لامبداهای ناهمزمان را به توابع مرتبه بالاتر ارسال کنیم:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
تابع withRetry یک لامبدا suspend میپذیرد و در صورت خطا اجرای آن را تکرار میکند. این یک الگوی معمول برای درخواستهای شبکه با تلاش مجدد است.
تفاوتهای بین توابع suspend و معمولی فراتر از افزودن ساده اصلاحکننده است. بیایید تفاوتهای اساسی را بررسی کنیم.
| ویژگی | تابع معمولی | تابع suspend |
|---|---|---|
| رشته اجرا | رشته را تا پایان مسدود میکند | میتواند رشته را آزاد کرده و بعداً ادامه دهد |
| پارامترهای کامپایلر | فقط پارامترهای مشخص شده | Continuation ضمنی در انتها |
| فراخوانی از تابع معمولی | بله | خیر |
| پشته | پشته فیزیکی رشته | ماشین حالت در heap + پشته فیزیکی بین نقاط |
| بازگشت | مقدار مستقیم | مقدار یا COROUTINE_SUSPENDED |
| عملکرد | سربار حداقل | ~چند نانوثانیه برای ماشین حالت (Kotlin 1.9+) |
تابع معمولی Continuation ندارد — جایی برای ذخیره حالت و ازسرگیری اجرا ندارد. اگر نیاز به فراخوانی تابع suspend از تابع معمولی دارید، از runBlocking (برای تست) یا CoroutineScope.launch (برای تولید با در نظر گرفتن چرخه حیات) استفاده کنید.
سه سناریوی واقعی استفاده از توابع suspend در برنامههای Android با Kotlin را بررسی میکنیم.
Room مستقیماً از توابع suspend پشتیبانی میکند — درخواست به طور خودکار در رشته پسزمینه اجرا میشود:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room داخلیاً از Dispatchers.IO برای اجرای درخواست استفاده میکند و نتیجه به توزیعکنندهای که تابع suspend در آن فراخوانی شده بود بازگردانده میشود.
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch یک کوروتین ایجاد میکند که داخل آن تابع suspend getProfile فراخوانی میشود. پس از دریافت نتیجه، UI در رشته اصلی بهروزرسانی میشود.
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
سه تابع suspend به ترتیب اجرا میشوند. در هر مرحله کوروتین میتواند بدون مسدود کردن رشته متوقف شود. اگر مرحلهای استثنا ایجاد کند — بقیه اجرا نمیشوند که از وضعیتهای نادرست سفارش محافظت میکند.
حتی برنامهنویسان با تجربه Kotlin در طراحی توابع suspend اشتباه میکنند. رایجترین موارد را بررسی میکنیم.
تابع suspend کد را به طور خودکار ناهمزمان نمیکند. Thread.sleep()، InputStream.read() و سایر فراخوانیهای مسدودکننده همچنان رشته را مسدود میکنند. برای بستن عملیات مسدودکننده از withContext(Dispatchers.IO) استفاده کنید.
اگر تابعی توابع suspend دیگر را فراخوانی نمیکند و عملیات ناهمزمان انجام نمیدهد — اصلاحکننده suspend اضافی است. سربار ماشین حالت را اضافه میکند و زمینه فراخوانی را محدود میکند. تابع را فقط زمانی suspend کنید که واقعاً متوقف میشود.
هنگام لغو کوروتین، توابع suspend CancellationException پرتاب میکنند. آن را بیدلیل نگیرید — کد فراخوانیکننده را از توانایی کامل کردن صحیح لغو محروم میکنید. اگر نیاز به انجام عملیات نهاییسازی دارید، از بلوک finally و NonCancellable استفاده کنید.
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
بلوک finally همیشه اجرا میشود، از جمله لغو، و NonCancellable تضمین میکند که پاکسازی قطع نشود.
نمیتوان تابع suspend را مستقیماً از callback بدون ایجاد کوروتین فراخوانی کرد. برای تطبیق سبک callback با کوروتینها از suspendCoroutine یا suspendCancellableCoroutine استفاده کنید.
سوالات متداول
بله، از نظر فنی تابع suspend میتواند توابع suspend دیگر را فراخوانی نکند. کامپایلر یک ماشین حالت با یک حالت (label 0) ایجاد میکند. با این حال، چنین تابعی فایده عملی ندارد — مانند تابع معمولی اجرا میشود اما با سربار. بدون نیاز از suspend استفاده نکنید.
Kotlin کتابخانه kotlinx-coroutines-debug را ارائه میدهد — با DebugProbes و ابزارهای ردیابی کوروتین. در Android Studio از نسخه Arctic Fox به بعد، یک برگه داخلی Coroutines در Debugger وجود دارد که کوروتینهای فعال، وضعیت و نقاط توقف آنها را نشان میدهد.
هر نقطه توقف یک حالت جدید در ماشین حالت ایجاد میکند. برای اکثر برنامهها سربار یک نقطه واحد نانوثانیههاست (Kotlin 1.9+). فقط در صورت دهها هزار نقطه در حلقه، بهتر است عملیات را ترکیب کرده یا از sequence/flow استفاده کنید.
در Kotlin suspend اصلاحکننده نوع تابع است نه نشانگر مقدار بازگشتی (مانند async در C#). هر تابع suspend میتواند پارامترها و نوع بازگشتی دلخواه داشته باشد و فراخوانی آن از نظر نحوی با فراخوانی تابع معمولی تفاوتی ندارد — عملگر await در محل فراخوانی وجود ندارد.
برای تطبیق از suspendCancellableCoroutine استفاده کنید. داخل آن، ثبت callback را ارسال میکنید که continuation.resume() را فراخوانی میکند و در صورت پشتیبانی callback از لغو اشتراک، توکن لغو را برمیگردانید. این یک الگوی استاندارد برای بستن APIهای قدیمی Android است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید