withContext: چیست، تغییر زمینه و کار در کوروتین‌ها

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

withContext — تابع تغییردهنده زمینه اجرا در داخل هم‌برنامه است که به طور موقت نخ یا توزیع‌کننده را برای بلوک کد مشخص شده تغییر می‌دهد و نتیجه را به زمینه اصلی بازمی‌گرداند. طبق داده‌های JetBrains, 2025، withContext یکی از پرکاربردترین ابزارهای کوروتین برای کار با درخواست‌های شبکه و عملیات دیسک است. این تابع تضمین می‌کند که پس از اتمام بلوک، کوروتین اجرا را روی توزیع‌کننده اصلی ادامه دهد که از خطاهای تصادفی امنیت نخ جلوگیری می‌کند.

نکات اصلی

  • withContext — تابع تعلیقی که CoroutineContext را برای بلوک کد داده شده تغییر می‌دهد و نتیجه را برمی‌گرداند
  • Dispatchers.IO — آرگومان معمول برای تغییر به نخ پس‌زمینه در عملیات شبکه و دیسک
  • Dispatchers.Main — زمینه اصلی که withContext پس از اتمام بلوک به طور خودکار اجرا را به آن بازمی‌گرداند
  • فراخوانی‌های ترتیبی — withContext کد را به صورت ترتیبی اجرا می‌کند، بر خلاف launch و async، که کنترل ترتیب عملیات را ساده‌تر می‌کند
  • نتیجه val — withContext مقدار را مستقیماً از طریق return در آخرین خط لامبدا برمی‌گرداند، بدون await یا join

withContext در Kotlin چیست؟

withContext — تابع تعلیقی از بسته kotlinx.coroutines است که بلوک کد داده شده را در CoroutineContext مشخص شده اجرا می‌کند و نتیجه را به زمینه اصلی بازمی‌گرداند. امضای تابع به این صورت است:

kotlin
suspend fun  withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

پارامتر context هر CoroutineContext را می‌پذیرد — اغلب یکی از Dispatchers.IO، Dispatchers.Default یا Dispatchers.Main استاندارد. بلوک دقیقاً در این زمینه اجرا می‌شود و نتیجه به جایی که withContext فراخوانی شده بازگردانده می‌شود.

ویژگی کلیدی: بازگشت خودکار

پس از اتمام لامبدا، withContext به طور تضمینی اجرا را به توزیع‌کننده اصلی بازمی‌گرداند. این بدان معناست که توسعه‌دهنده نیازی به فراخوانی دستی withContext(Dispatchers.Main) پس از عملیات پس‌زمینه ندارد — بازگشت به طور خودکار انجام می‌شود. این رفتار در مشخصات Kotlin Coroutines از نسخه 1.3 تثبیت شده است.

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

توسعه اندروید — حوزه اصلی کاربرد withContext است. سناریوی معمول: ViewModel یک کوروتین روی نخ اصلی راه‌اندازی می‌کند، داخل آن withContext(Dispatchers.IO) برای درخواست شبکه فراخوانی می‌شود و نتیجه پس از بازگشت خودکار به Main برای به‌روزرسانی UI استفاده می‌شود. این رویکرد اساس معماری MVVM را تشکیل می‌دهد و توسط Google در راهنمای رسمی کوروتین‌ها توصیه می‌شود.

withContext چگونه کار می‌کند: تغییر توزیع‌کننده‌ها

برای درک withContext، باید با CoroutineContext و مؤلفه کلیدی آن — توزیع‌کننده (Dispatcher) آشنا شوید. هر هم‌برنامه مجموعه‌ای از عناصر زمینه دارد که در میان آنها توزیع‌کننده تعیین می‌کند کد روی کدام نخ یا مجموعه نخ‌ها اجرا شود.

توزیع‌کننده‌های استاندارد for withContext

توزیع‌کنندهکاربرداندازه مجموعه
Dispatchers.Mainنخ اصلی UI (Android, JavaFX, Swing)1 (نخ اصلی)
Dispatchers.IOعملیات دیسک و شبکه64 نخ (محدودیت افزایش می‌یابد)
Dispatchers.Defaultمحاسبات سنگین CPUmax(2, تعداد هسته‌ها)
Dispatchers.Unconfinedبدون نخ ثابتنامحدود

درک این نکته مهم است که withContext یک کوروتین جدید ایجاد نمی‌کند — فقط زمینه را تغییر می‌دهد برای کوروتین موجود. این تفاوت کلیدی با launch و async است که هم‌برنامه‌های جدیدی ایجاد می‌کنند. پیاده‌سازی داخلی withContext بهینه شده است: اگر زمینه درخواستی با زمینه فعلی مطابقت داشته باشد، تغییری رخ نمی‌دهد — تابع روی همان توزیع‌کننده اجرا می‌شود.

زمانی که withContext نخ را تغییر نمی‌دهد

Dispatchers.Main در داخل withContext(Dispatchers.Main) باعث تغییر نمی‌شود — Kotlin Coroutines یکسانی زمینه‌ها را تشخیص می‌دهد و عملیات اضافی را رد می‌کند. به طور مشابه، withContext(Dispatchers.Default) در داخل کوروتینی که قبلاً روی Default کار می‌کند، هیچ سرباری ایجاد نمی‌کند. این بهینه‌سازی در ContinuationInterceptor پیاده‌سازی شده است.

withContext در مقابل launch و async: چه زمانی کدام را انتخاب کنیم

تازه‌کارها اغلب withContext را با launch و async اشتباه می‌گیرند، زیرا هر سه تابع با کوروتین‌ها و زمینه کار می‌کنند. با این حال، هدف آنها اساساً متفاوت است.

مقایسه سه تابع

ویژگیwithContextlaunchasync
کوروتین جدید ایجاد می‌کندخیربلهبله
نتیجه برمی‌گرداندبله (T مستقیم)خیر (Job)بله (Deferred<T>)
اجراترتیبیموازیموازی
انتظار برای نتیجهخودکارjoin()await()
کاربرد معمولتغییر توزیع‌کنندهFire-and-forgetمحاسبات موازی

قانون انتخاب

اگر نیاز به اجرای یک عملیات در نخ پس‌زمینه و دریافت نتیجه دارید — از withContext استفاده کنید. اگر نیاز به اجرای چند عملیات مستقل به صورت موازی دارید — از async با await استفاده کنید. اگر نتیجه لازم نیست (ثبت‌日志، نوشتن کش) — launch. Google با withContext را به عنوان ابزار ترجیحی برای لایه Repository در معماری Android توصیه می‌کند.

نمونه کدهای withContext

سه سناریوی عملی استفاده از withContext در برنامه‌های Android با Kotlin را بررسی می‌کنیم. هر نمونه یک وظیفه خاص و الگوی صحیح را نشان می‌دهد.

نمونه 1: درخواست شبکه در Repository

ViewModel متد مخزن را از کوروتین روی Main فراخوانی می‌کند. در داخل withContext(Dispatchers.IO) درخواست HTTP اجرا می‌شود و نتیجه به طور خودکار بازگردانده می‌شود:

kotlin
class UserRepository(
    private val api: UserApi
) {
    suspend fun getUser(id: String): User {
        return withContext(Dispatchers.IO) {
            api.fetchUser(id)
        }
    }
}

کوروتین در ViewModel getUser را مانند یک تابع تعلیق معمولی فراخوانی می‌کند — بدون تعیین صریح توزیع‌کننده. withContext جزئیات تغییر نخ را پنهان می‌کند.

نمونه 2: دو عملیات متوالی پس‌زمینه

زمانی که نیاز به اجرای چند عملیات IO پشت سر هم است، withContext آنها را در یک بلوک واحد ترکیب می‌کند. این کارآمدتر از قرار دادن هر عملیات در یک withContext جداگانه است:

kotlin
suspend fun loadUserProfile(id: String): Profile {
    return withContext(Dispatchers.IO) {
        val user = api.fetchUser(id)
        val posts = api.fetchPosts(id)
        Profile(user, posts)
    }
}

هر دو عملیات روی Dispatchers.IO اجرا می‌شوند و نتیجه Profile بدون تغییر زمینه اضافی ایجاد و بازگردانده می‌شود. اگر عملیات‌ها مستقل هستند، بهتر است برای اجرای موازی از async استفاده کنید.

نمونه 3: زمینه ترکیبی با NonCancellable

در برخی سناریوها نیاز به اجرای کدی است که نمی‌توان آن را لغو کرد — مثلاً ذخیره وضعیت هنگام بسته شدن صفحه. ترکیب withContext + NonCancellable این وظیفه را حل می‌کند:

kotlin
withContext(Dispatchers.IO + NonCancellable) {
    cache.saveState(state)
    analytics.logEvent("state_saved")
}

عملگر + دو عنصر زمینه را ترکیب می‌کند: توزیع‌کننده IO و پرچم NonCancellable. بلوک حتی اگر کوروتین والد لغو شده باشد اجرا می‌شود — این برای عملیات نهایی‌سازی مفید است.

در پشت صحنه چه می‌گذرد: Continuation و بهینه‌سازی‌ها

پیاده‌سازی داخلی withContext بر اساس مکانیزم Continuation — انتزاع مرکزی کوروتین‌های Kotlin است. هر نقطه تعلیق (suspend point) وضعیت اجرا را در شیء Continuation ذخیره می‌کند و withContext نیز از این قاعده مستثنی نیست.

withContext چگونه زمینه را در سطح بایت‌کد تغییر می‌دهد

کامپایلر Kotlin withContext را به فراخوانی متد withContext از kotlinx.coroutines ترجمه می‌کند که در داخل یک نمونه جدید DispatchedContinuation ایجاد می‌کند. این شیء Continuation اصلی را می‌پوشاند و توزیع‌کننده را در آن جایگزین می‌کند. اگر توزیع‌کننده جدید با فعلی متفاوت باشد، اجرا معلق می‌شود، بلوک به مجموعه نخ‌های مربوطه ارسال می‌شود و پس از اتمام با زمینه اصلی از سر گرفته می‌شود.

بهینه‌سازی: fast-path در صورت تطابق زمینه‌ها

زمانی که withContext با همان توزیع‌کننده‌ای فراخوانی می‌شود که کوروتین قبلاً روی آن در حال اجراست، Kotlin fast-path را فعال می‌کند: بلوک به صورت همزمان، بدون ایجاد DispatchedContinuation و بدون ارسال به مجموعه نخ‌ها اجرا می‌شود. این باعث می‌شود withContext در فراخوانی‌های مکرر با همان زمینه عملاً بدون هزینه باشد. طبق بنچمارک‌های JetBrains (kotlinx.coroutines 1.8)، fast-path در کمتر از 0.1 میکروثانیه اجرا می‌شود.

محدودیت‌ها از نظر عملکرد

هر فراخوانی withContext با توزیع‌کننده متفاوت یک DispatchedContinuation جدید ایجاد می‌کند و نیاز به تغییر نخ دارد — این بسته به بار از 1 تا 5 میکروثانیه زمان می‌برد. برای اکثر برنامه‌ها این تأخیر نامحسوس است، اما در حلقه‌هایی با هزاران تکرار، بهتر است عملیات‌ها را در یک بلوک withContext تجمیع کنید.

خطاهای رایج در استفاده از withContext

حتی توسعه‌دهندگان با تجربه هنگام کار با withContext اشتباه می‌کنند. چهار مشکل رایج و راه‌های پیشگیری از آنها را بررسی می‌کنیم.

خطای 1: withContext تودرتو بدون نیاز

توسعه‌دهندگان اغلب هر خط را در یک withContext جداگانه قرار می‌دهند، به جای اینکه عملیات‌ها را در یک بلوک ترکیب کنند. هر فراخوانی اضافی با توزیع‌کننده متفاوت سربار ایجاد می‌کند.

درست: عملیات‌های IO متوالی را در یک withContext(Dispatchers.IO) { ... } ترکیب کنید. اگر بخشی از عملیات‌ها CPU-فشرده هستند — از withContext(Dispatchers.Default) در داخل همان بلوک استفاده کنید.

خطای 2: استفاده از withContext به جای async برای وظایف موازی

withContext کد را به صورت ترتیبی اجرا می‌کند. اگر دو درخواست شبکه مستقل در یک withContext قرار داده شوند، یکی پس از دیگری اجرا می‌شوند. برای موازی‌سازی از async + await استفاده کنید.

kotlin
// ترتیبی — کند
withContext(Dispatchers.IO) {
    val a = api.fetchA()
    val b = api.fetchB()
}

// موازی — سریع
coroutineScope {
    val a = async { api.fetchA() }
    val b = async { api.fetchB() }
    println("${a.await()} ${b.await()}")
}

خطای 3: فراموش کردن NonCancellable در عملیات بحرانی

اگر کوروتین در حین withContext لغو شود، بلوک روی Dispatchers.IO نیز قطع می‌شود. برای عملیاتی که باید به هر قیمتی تکمیل شوند (نوشتن در پایگاه داده، ارسال آمار)، withContext را با NonCancellable ترکیب کنید.

خطای 4: تغییر وضعیت UI در داخل بلوک IO

هرگز کامپوننت‌های View را در داخل withContext(Dispatchers.IO) به‌روز نکنید. withContext تا پایان بلوک به Main بازنمی‌گردد. به‌روزرسانی UI را بعد از براکت بسته withContext انجام دهید — در آن زمان کوروتین قبلاً روی نخ اصلی خواهد بود.

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

withContext چه تفاوتی با runBlocking دارد؟

withContext — تابع تعلیقی است که نخ را مسدود نمی‌کند، بلکه زمینه را در داخل کوروتین موجود تغییر می‌دهد. runBlocking — پلی بین کوروتین‌ها و کد معمولی است که نخ فعلی را تا اتمام مسدود می‌کند. withContext برای نخ UI ایمن است، runBlocking — نیست.

آیا می‌توان از withContext بدون suspend استفاده کرد؟

خیر، withContext یک تابع تعلیقی است، بنابراین فقط می‌توان آن را از تابع تعلیقی دیگر یا از کوروتین (launch/async) فراخوانی کرد. from یک تابع معمولی withContext فراخوانی نمی‌شود — برای این کار به runBlocking یا CoroutineScope نیاز است.

اگر همان توزیع‌کننده را به withContext ارسال کنم چه می‌شود؟

Kotlin fast-path را فعال می‌کند — بلوک به صورت همزمان روی همان نخ بدون تغییر اجرا می‌شود. سربار کمتر از 0.1 میکروثانیه است. این یک خطا نیست، اما چنین فراخوانی اضافی است — بهتر است کد را بدون withContext اجرا کنید.

withContext با استثناها چگونه کار می‌کند؟

استثناهای داخل withContext مانند کد معمولی — از طریق try-catch — منتشر می‌شوند. اگر بلوک استثنا پرتاب کند، به کوروتین والد منتشر می‌شود و در صورت عدم مدیریت، آن را لغو می‌کند. از try-catch در داخل withContext یا اطراف آن استفاده کنید.

آیا withContext یک کوروتین جدید ایجاد می‌کند؟

خیر، withContext یک کوروتین جدید ایجاد نمی‌کند. از هم‌برنامه موجود استفاده می‌کند اما به طور موقت زمینه آن را تغییر می‌دهد. این آن را از launch و async متمایز می‌کند که کوروتین‌های فرزند ایجاد می‌کنند. این رفتار توسط کد منبع kotlinx.coroutines تأیید شده است.

خلاصه

  • withContext — تابع تعلیقی برای تغییر CoroutineContext در داخل کوروتین موجود با بازگشت خودکار به زمینه اصلی
  • Dispatchers.IO — توزیع‌کننده اصلی برای درخواست‌های شبکه و عملیات دیسک در داخل withContext
  • Fast-path — بهینه‌سازی Kotlin که در آن withContext با همان توزیع‌کننده به صورت همزمان بدون سربار اجرا می‌شود
  • وظایف موازی نیاز به async/await دارند، نه withContext — withContext کد را به صورت ترتیبی اجرا می‌کند
  • NonCancellable — پرچمی برای عملیات بحرانی در داخل withContext که نباید در هنگام لغو کوروتین قطع شوند
  • لایه Repository — مکان توصیه شده برای withContext در معماری Android طبق راهنماهای Google
  • Continuation — مکانیزمی که تغییر زمینه در withContext در سطح بایت‌کد Kotlin بر اساس آن ساخته شده است

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

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

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

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