
اگر توسعهدهنده اندروید هستید، احتمال زیاد با این صحنهها روبهرو شدهاید:
صفحهای که بارگذاریش کُند است، درخواست شبکهای که UI را قفل میکند، چند فراخوانی API که انگار قرار نیست تمام بشوند، یا یک کار پسزمینه که انیمیشنهای نرم و روان شما را لگدار میکند.
آشناست؟ 😅
خیالتان راحت، تنها نیستید. همینجاست که Kotlin Coroutines — و بهخصوص درک تفاوت بین launch و async — وارد میدان میشوند و میتوانند بازی را برای عملکرد اپ شما عوض کنند.
ماه پیش، روی یک اپلیکیشن شبکه اجتماعی کار میکردم. توی صفحه پروفایل باید هم اطلاعات کاربر و هم پستهای اخیرش نمایش داده میشد.
کد ساده و اولیه این بود:
class ProfileViewModel : ViewModel() { fun loadProfile(userId: String) { viewModelScope.launch { val user = userRepository.getUser(userId) // ۱ ثانیه طول میکشد val posts = postRepository.getPosts(userId) // ۱ ثانیه طول میکشد // مجموعاً: ۲ ثانیه _profileState.value = ProfileState(user, posts) } } }
نتیجه؟ کاربر باید ۲ ثانیه کامل فقط منتظر میماند تا پروفایلش بالا بیاید.
نه فاجعه است، نه خیلی خوب. بدتر این که این دو درخواست API اصلاً به هم ربطی نداشتند و میشد آنها را همزمان اجرا کرد.
اینجاست که دانستن فرق launch و async میتواند معجزه کند.
قبل از این که برویم سراغ تکنیکها، بیایید اول خیلی ساده بفهمیم هر کدام چه کار میکنند.
launch — کارگر «بفرست و ولش کن»
launch را تصور کنید مثل این که یک نفر را برای انجام کاری بفرستید ولی انتظار نداشته باشید گزارشی برگرداند. کار خودش را میکند و تمام. شما هم رسید کاری که دادهاید (یک Job) را میگیرید و سراغ کار خودتان میروید.
val job = launch { performBackgroundTask() updateDatabase() // هیچ مقدار بازگشتی وجود ندارد }
ویژگیها:
یک Job برمیگرداند
هیچ مقداری برنمیگرداند
برای کارهایی که فقط اثر جانبی دارند و خروجی مهم نیست، عالی است
async — کارگر «قول نتیجه»
async را تصور کنید مثل استخدام یک مشاور که حتماً میخواهید بعد از بررسیاش گزارش کامل بدهد. وقتی async را صدا میزنید، یک وعده نتیجه آینده (Deferred) میگیرید، بعد هر وقت خواستید میتوانید با await() نتیجه را بگیرید.
val deferred = async { calculateComplexValue() } val result = deferred.await()
ویژگیها:
یک Deferred برمیگرداند
با await() میتوانید مقدار خروجی را بگیرید
همزمانی را راحت میکند (کارهای موازی)
سناریو ۱: سریعتر کردن راهاندازی اپ
فرض کنید هنگام باز شدن اپ باید سه کار انجام شود: بارگذاری تنظیمات کاربر، چک کردن آپدیت، و همگامسازی دادهها.
❌ روش کند (ترتیبی): هر کدام بعد از دیگری اجرا میشوند.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { loadUserPreferences() // 500 میلیثانیه checkForUpdates() // 800 میلیثانیه syncUserData() // 1200 میلیثانیه کل: 2.5 ثانیه } } }
✅ روش سریع (همزمان): همه با هم اجرا میشوند و فقط طولانیترینشان زمان کلی را تعیین میکند.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { val prefsDeferred = async { loadUserPreferences() } val updatesDeferred = async { checkForUpdates() } val syncDeferred = async { syncUserData() } // همه به طور همزمان اجرا میشوند، زمان کل: ~1.2 ثانیه val prefs = prefsDeferred.await() val updates = updatesDeferred.await() val syncResult = syncDeferred.await() initializeApp(prefs, updates, syncResult) } } }
سناریو ۲: ساخت فید خبری سریعتر
اگر همزمان مقالات، اطلاعات کاربر و تبلیغات را دریافت کنیم، بهجای ۳ ثانیه، کمتر از ۱ ثانیه طول میکشد.
class NewsFeedViewModel : ViewModel() { fun loadFeed() { viewModelScope.launch { try { // همه عملیات را به طور همزمان شروع کن val articlesDeferred = async { newsRepository.getLatestArticles() } val userDeferred = async { userRepository.getCurrentUser() } val adsDeferred = async { adRepository.getTargetedAds() } // منتظر همه نتایج باش val articles = articlesDeferred.await() val user = userDeferred.await() val ads = adsDeferred.await() // نتایج را ترکیب کن val feedData = FeedData(articles, user, ads) _feedState.value = FeedState.Success(feedData) } catch (e: Exception) { _feedState.value = FeedState.Error(e.message) } } } }
استفاده از launch وقتی به نتیجه نیاز داریم → باید async باشد.
// ❌ اشتباه - تلاش برای گرفتن نتایج از launch fun fetchUserData(): User? { var user: User? = null launch { user = userRepository.getUser() // این طور که انتظار دارید کار نمیکند } return user // همیشه null خواهد بود } // ✅ درست - استفاده از async برای نتایج suspend fun fetchUserData(): User { return async { userRepository.getUser() }.await() }
صدا زدن زودهنگام await() که باعث میشود همزمانی از بین برود.
// ❌ عدم استفاده از همزمانی suspend fun loadData() { val user = async { getUserData() }.await() val posts = async { getPostsData() }.await() // اینها به صورت ترتیبی اجرا میشوند، هدف را از بین میبرد } // ✅ اجرای همزمان مناسب suspend fun loadData() { val userDeferred = async { getUserData() } val postsDeferred = async { getPostsData() } val user = userDeferred.await() val posts = postsDeferred.await() }
فراموش کردن مدیریت استثناءها در کارهای همزمان.
// ❌ مدیریت ناکامل خطا viewModelScope.launch { val result1 = async { riskyOperation1() } val result2 = async { riskyOperation2() } // اگر result1.await() خطا پرتاب کند، result2.await() هرگز فراخوانی نمیشود val data1 = result1.await() val data2 = result2.await() } // ✅ مدیریت مناسب استثناء viewModelScope.launch { try { val result1 = async { riskyOperation1() } val result2 = async { riskyOperation2() } val data1 = result1.await() val data2 = result2.await() // مدیریت موفقیت } catch (e: Exception) { // مدیریت هر گونه شکست } }
از launch استفاده کنید وقتی:
فقط کارهای «آتش و فراموش کن» انجام میدهید (مثل لاگ کردن، کش کردن)
خروجی برایتان مهم نیست
از async استفاده کنید وقتی:
به داده خروجی نیاز دارید
چند عملیات مستقل دارید که میخواهید موازی اجرا شوند
از coroutineScope برای گروهبندی چند کار همزمان استفاده کنید
suspend fun loadCompleteProfile() = coroutineScope { val basicInfo = async { userRepository.getBasicInfo() } val preferences = async { userRepository.getPreferences() } val statistics = async { userRepository.getStatistics() } ProfileData( basic = basicInfo.await(), prefs = preferences.await(), stats = statistics.await() ) }
برای عملیات طولانی withTimeout بگذارید
suspend fun loadDataWithTimeout() = withTimeout(5000) { val data1 = async { fetchData1() } val data2 = async { fetchData2() } CombinedData(data1.await(), data2.await()) }
برای مجموعهای از کارها از awaitAll() استفاده کنید
suspend fun loadMultipleUsers(userIds: List<String>): List<User> { return userIds.map { userId -> async { userRepository.getUser(userId) } }.awaitAll() }
تفاوت launch و async فقط یک قاعده فنی نیست — مستقیماً روی تجربه کاربری تاثیر میگذارد.
کاربر شاید نداند شما از coroutine استفاده کردهاید، اما قطعاً حس میکند وقتی صفحه به جای ۳ ثانیه، در ۱ ثانیه لود میشود.
یادتان باشد:
launch برای کارهایی که نتیجه لازم ندارید
async برای مواقعی که نتیجه میخواهید یا میتوانید کارها را همزمان انجام دهید
خطاها را حتماً مدیریت کنید
await() را آخر کار صدا بزنید