suspend function: این چیست، نحو و کار در کوروتین‌ها

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

Suspend function — تابعی با اصلاح‌کننده suspend است که می‌تواند اجرای خود را بدون مسدود کردن رشته متوقف کرده و بعداً در همان کوروتین ادامه دهد. بر اساس JetBrains Kotlin Docs, 2025، توابع suspend بلوک ساختمانی بنیادی کوروتین‌ها هستند و ناهمزمانی را بدون callback فراهم می‌کنند. هر تابع suspend به یک ماشین حالت مبتنی بر Continuation کامپایل می‌شود که امکان مدیریت مؤثر نقاط توقف را فراهم می‌کند.

نکات اصلی

  • Suspend — کلمه کلیدی Kotlin که یک تابع را به عنوان قابل توقف (ناهمزمان) علامت‌گذاری می‌کند
  • Continuation — پارامتر پنهانی که کامپایلر برای حفظ حالت به هر تابع suspend اضافه می‌کند
  • نقاط توقف — مکان‌های فراخوانی سایر توابع suspend که کوروتین می‌تواند بدون مسدود کردن متوقف شود
  • ماشین حالت — نمایش داخلی تابع suspend که در آن هر نقطه توقف یک حالت جداگانه است
  • فراخوانی فقط از کوروتین — توابع suspend فقط از تابع suspend دیگر یا از launch/async قابل فراخوانی هستند

suspend function در Kotlin چیست؟

Suspend function — تابعی است که با کلمه کلیدی suspend اعلان شده و می‌تواند اجرا را در یک یا چند نقطه بدون مسدود کردن رشته متوقف کند. هر فراخوانی تابع suspend در داخل تابع suspend دیگر یک نقطه توقف بالقوه است.

kotlin
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 چگونه کار می‌کنند: Continuation و ماشین حالت

درک ساختار داخلی توابع suspend کلید کار صحیح با کوروتین‌هاست. برخلاف توابع معمولی، هر تابع suspend به کلاسی با رابط Continuation کامپایل می‌شود.

Continuation — پارامتر پنهان

کامپایلر Kotlin یک پارامتر از نوع Continuation به انتهای هر پارامتر تابع suspend اضافه می‌کند. Continuation شامل موارد زیر است:

  • context — CoroutineContext (توزیع‌کننده، job، عناصر زمینه)
  • resumeWith — روش ازسرگیری اجرا با نتیجه یا استثنا
  • label — شاخص حالت فعلی در ماشین حالت

ماشین حالت با مثال

فرض کنید یک تابع suspend با دو فراخوانی به توابع suspend دیگر داریم:

kotlin
suspend fun process() {
    val a = stepOne()
    val b = stepTwo(a)
    println(b)
}

کامپایلر آن را به ماشین حالت با برچسب‌ها تبدیل می‌کند:

kotlin
// نمایش ساده‌شده کد تولیدشده
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 تفاوتی با تابع معمولی ندارد، به جز کلمه کلیدی suspend قبل از fun. فقط یک محدودیت وجود دارد: تابع suspend را فقط می‌توان از کوروتین یا تابع suspend دیگر فراخوانی کرد.

اعلان پایه

kotlin
suspend fun delayAndReturn(ms: Long): String {
    delay(ms)
    return "Done after ${ms}ms"
}

در این مثال delay نیز یک تابع suspend است که کوروتین را برای تعداد میلی‌ثانیه مشخصی بدون مسدود کردن رشته متوقف می‌کند. پس از تأخیر، اجرا ازسر گرفته می‌شود.

فراخوانی از کوروتین

kotlin
fun main() = runBlocking {
    val result = delayAndReturn(1000)
    println(result)
}

runBlocking پلی بین دنیای معمولی و کوروتین‌ها ایجاد می‌کند. در داخل لامبدا، فراخوانی هر تابع suspend مجاز است.

لامبداهای suspend و انواع تابعی

Kotlin از نسخه‌های suspend انواع تابعی پشتیبانی می‌کند — suspend () -> T و suspend (A) -> B. این امکان را می‌دهد که لامبداهای ناهمزمان را به توابع مرتبه بالاتر ارسال کنیم:

kotlin
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 و معمولی فراتر از افزودن ساده اصلاح‌کننده است. بیایید تفاوت‌های اساسی را بررسی کنیم.

ویژگیتابع معمولیتابع suspend
رشته اجرارشته را تا پایان مسدود می‌کندمی‌تواند رشته را آزاد کرده و بعداً ادامه دهد
پارامترهای کامپایلرفقط پارامترهای مشخص شدهContinuation ضمنی در انتها
فراخوانی از تابع معمولیبلهخیر
پشتهپشته فیزیکی رشتهماشین حالت در heap + پشته فیزیکی بین نقاط
بازگشتمقدار مستقیممقدار یا COROUTINE_SUSPENDED
عملکردسربار حداقل~چند نانوثانیه برای ماشین حالت (Kotlin 1.9+)

چرا توابع suspend را نمی‌توان از توابع معمولی فراخوانی کرد

تابع معمولی Continuation ندارد — جایی برای ذخیره حالت و ازسرگیری اجرا ندارد. اگر نیاز به فراخوانی تابع suspend از تابع معمولی دارید، از runBlocking (برای تست) یا CoroutineScope.launch (برای تولید با در نظر گرفتن چرخه حیات) استفاده کنید.

نمونه‌های توابع suspend در Android

سه سناریوی واقعی استفاده از توابع suspend در برنامه‌های Android با Kotlin را بررسی می‌کنیم.

مثال 1: Room DAO با درخواست‌های suspend

Room مستقیماً از توابع suspend پشتیبانی می‌کند — درخواست به طور خودکار در رشته پس‌زمینه اجرا می‌شود:

kotlin
@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 در آن فراخوانی شده بود بازگردانده می‌شود.

مثال 2: ترکیب توابع suspend برای بارگذاری صفحه

kotlin
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 در رشته اصلی به‌روزرسانی می‌شود.

مثال 3: مراحل ناهمزمان متوالی

kotlin
suspend fun placeOrder(cart: Cart): OrderResult {
    val validated = validateCart(cart)
    val payment = processPayment(validated)
    val receipt = sendReceipt(payment)
    return receipt
}

سه تابع suspend به ترتیب اجرا می‌شوند. در هر مرحله کوروتین می‌تواند بدون مسدود کردن رشته متوقف شود. اگر مرحله‌ای استثنا ایجاد کند — بقیه اجرا نمی‌شوند که از وضعیت‌های نادرست سفارش محافظت می‌کند.

خطاهای رایج هنگام کار با توابع suspend

حتی برنامه‌نویسان با تجربه Kotlin در طراحی توابع suspend اشتباه می‌کنند. رایج‌ترین موارد را بررسی می‌کنیم.

خطای 1: فراخوانی‌های مسدودکننده داخل suspend

تابع suspend کد را به طور خودکار ناهمزمان نمی‌کند. Thread.sleep()، InputStream.read() و سایر فراخوانی‌های مسدودکننده همچنان رشته را مسدود می‌کنند. برای بستن عملیات مسدودکننده از withContext(Dispatchers.IO) استفاده کنید.

خطای 2: ایجاد توابع suspend بدون نیاز

اگر تابعی توابع suspend دیگر را فراخوانی نمی‌کند و عملیات ناهمزمان انجام نمی‌دهد — اصلاح‌کننده suspend اضافی است. سربار ماشین حالت را اضافه می‌کند و زمینه فراخوانی را محدود می‌کند. تابع را فقط زمانی suspend کنید که واقعاً متوقف می‌شود.

خطای 3: نادیده گرفتن CancellationException

هنگام لغو کوروتین، توابع suspend CancellationException پرتاب می‌کنند. آن را بی‌دلیل نگیرید — کد فراخوانی‌کننده را از توانایی کامل کردن صحیح لغو محروم می‌کنید. اگر نیاز به انجام عملیات نهایی‌سازی دارید، از بلوک finally و NonCancellable استفاده کنید.

kotlin
suspend fun safeOperation() {
    try {
        doWork()
    } finally {
        withContext(NonCancellable) {
            cleanup()
        }
    }
}

بلوک finally همیشه اجرا می‌شود، از جمله لغو، و NonCancellable تضمین می‌کند که پاک‌سازی قطع نشود.

خطای 4: فراخوانی توابع suspend از callbackها

نمی‌توان تابع suspend را مستقیماً از callback بدون ایجاد کوروتین فراخوانی کرد. برای تطبیق سبک callback با کوروتین‌ها از suspendCoroutine یا suspendCancellableCoroutine استفاده کنید.

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

آیا تابع suspend می‌تواند نقاط توقف نداشته باشد؟

بله، از نظر فنی تابع suspend می‌تواند توابع suspend دیگر را فراخوانی نکند. کامپایلر یک ماشین حالت با یک حالت (label 0) ایجاد می‌کند. با این حال، چنین تابعی فایده عملی ندارد — مانند تابع معمولی اجرا می‌شود اما با سربار. بدون نیاز از suspend استفاده نکنید.

چگونه توابع suspend را دیباگ کنیم؟

Kotlin کتابخانه kotlinx-coroutines-debug را ارائه می‌دهد — با DebugProbes و ابزارهای ردیابی کوروتین. در Android Studio از نسخه Arctic Fox به بعد، یک برگه داخلی Coroutines در Debugger وجود دارد که کوروتین‌های فعال، وضعیت و نقاط توقف آنها را نشان می‌دهد.

آیا تعداد نقاط suspend بر عملکرد تأثیر می‌گذارد؟

هر نقطه توقف یک حالت جدید در ماشین حالت ایجاد می‌کند. برای اکثر برنامه‌ها سربار یک نقطه واحد نانوثانیه‌هاست (Kotlin 1.9+). فقط در صورت ده‌ها هزار نقطه در حلقه، بهتر است عملیات را ترکیب کرده یا از sequence/flow استفاده کنید.

تفاوت تابع suspend با async/await در زبان‌های دیگر چیست؟

در Kotlin suspend اصلاح‌کننده نوع تابع است نه نشانگر مقدار بازگشتی (مانند async در C#). هر تابع suspend می‌تواند پارامترها و نوع بازگشتی دلخواه داشته باشد و فراخوانی آن از نظر نحوی با فراخوانی تابع معمولی تفاوتی ندارد — عملگر await در محل فراخوانی وجود ندارد.

چگونه یک تابع callback را به suspend تبدیل کنیم؟

برای تطبیق از suspendCancellableCoroutine استفاده کنید. داخل آن، ثبت callback را ارسال می‌کنید که continuation.resume() را فراخوانی می‌کند و در صورت پشتیبانی callback از لغو اشتراک، توکن لغو را برمی‌گردانید. این یک الگوی استاندارد برای بستن APIهای قدیمی Android است.

خلاصه

  • Suspend function — تابعی با اصلاح‌کننده suspend که می‌تواند اجرا را بدون مسدود کردن رشته از طریق مکانیزم Continuation متوقف کند
  • ماشین حالت — نمایش داخلی تابع suspend در بایت‌کد Kotlin که هر نقطه توقف یک حالت جداگانه با label است
  • Continuation — پارامتر پنهان حاوی زمینه کوروتین و روش resumeWith برای ازسرگیری اجرا
  • فراخوانی فقط از کوروتین — توابع suspend بدون runBlocking یا CoroutineScope از توابع معمولی قابل دسترسی نیستند
  • عملیات مسدودکننده درون suspend نیاز به withContext(Dispatchers.IO) دارند — در غیر این صورت رشته مسدود می‌شود
  • Room و Retrofit بومی از توابع suspend پشتیبانی می‌کنند و رشته‌های پس‌زمینه را خودکار مدیریت می‌کنند
  • CancellationException — لغو را از طریق finally + NonCancellable مدیریت کنید، CancellationException را بی‌دلیل نگیرید

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

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

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

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