Main Thread mobil inkişafda — bu nədir, rolu və iş prinsipi

Müəllif: IT Sectr Dərc olunub: 2026-03-15 Oxuma vaxtı: 10 dəq

Main Thread — mobil tətbiqlərdə bütün istifadəçi interfeysini emal edən əsas icra thread-idir: toxunuşlar, çəkmə, layout yeniləməsi və animasiyalar. iOS-da bu RunLoop.main, Android-də — Looper.getMainLooper(). Bu thread-də hər hansı uzun müddətli əməliyyat UI-ni bloklayır və ANR (Android) və ya interfeysin donmasına (iOS) səbəb olur. Apple UIKit Documentation-ya görə, UI sinifləri thread təhlükəsiz deyil və yalnız Main Thread-dən çağırılmalıdır.

Əsas

  • Main Thread — iOS və Android-də UI-ni yeniləyə bilən yeganə thread
  • Bloklama Main Thread-in 5 saniyədən çox bloklanması ANR (Android) və ya interfeysin donmasına (iOS) səbəb olur
  • DispatchQueue.main (iOS) və runOnUiThread / Handler(Looper.getMainLooper()) (Android) — əsas thread-ə qayıtma yolları
  • iOS və Android UI framework-ləri thread-unsafe: UIKit, AppKit, Android View System
  • Main Thread Checker — fon thread-lərindən UI çağırışlarını aşkar etmək üçün Xcode daxili aləti

Main Thread nədir

Main Thread — tətbiq işə salındıqda əməliyyat sistemi tərəfindən yaradılan və bütün istifadəçi interfeysi hadisələrini emal edən thread-dir. Mobil platformalar kontekstində Main Thread həmçinin UI Thread adlanır, çünki çəkmə, toxunuşların işlənməsi və animasiya ilə bağlı bütün əməliyyatlar onun üzərində yerinə yetirilir. Hər bir tətbiqin dəqiq bir Main Thread-i var və bütün UI framework-ləri (UIKit, AppKit, Android Views, Compose UI) thread-unsafe-dir — digər thread-lərdən çağırıldıqda düzgün işə zəmanət vermirlər.

Architectural olaraq Main Thread Event Loop pattern-ni həyata keçirir: thread sonsuz olaraq yeni hadisələri (toxunuşlar, sistem bildirişləri, timer-lər) gözləyir və onları növbə sırası ilə emal edir. Bir hadisə emal edilərkən, növbəti növbədə gözləyir. Emal 100-200 millisaniyədən çox çəkərsə, istifadəçi gecikmə (jank) hiss edir. 5 saniyədən çox (Android) olarsa — sistem ANR (Application Not Responding) dialoqunu göstərir və tətbiqi bağlamağı təklif edir.

Main Thread-i anlamağın əhəmiyyətini qiymətləndirmək çətindir: mobil tətbiqlərdə performans problemlərinin 90%-nin mənbəyidir. Tərtibatçılar tez-tez ağır əməliyyatları (şəbəkə, fayllar, JSON parse, şəkil sıxma) fon thread-lərinə köçürməyi unudurlar. Emulatorda 10 millisaniyəyə yerinə yetirilən əməliyyat, real cihazda yavaş disk ilə 500 millisaniyə çəkə bilər və nəzərə çarpan laqa səbəb ola bilər.

Nə üçün UI yalnız Main Thread-də yenilənməlidir

Thread-unsafe UI framework-ləri — UIKit (2007) və Android-in (2008) ilk versiyalarından qoyulmuş memarlıq qərarıdır. Əsas səbəb performansdır: UI komponentlərinə girişin blokadalar (locks) vasitəsilə sinxronizasiyası hər çəkmə əməliyyatına əlavə yük qoyardı. Bunun əvəzinə framework-lər tələb edir ki, bütün UI dəyişiklikləri ciddi şəkildə bir thread-də yerinə yetirilsin, race condition-ları overhead olmadan aradan qaldırsın.

Təsəvvür edin ki, iki fon thread-i eyni anda textView.setText() çağırır. UI thread-safe olsaydı, hər iki çağırış mutex vasitəsilə sinxronlaşdırılardı ki, bu da çəkməni 20-40% yavaşladardı. Hazırkı memarlıqda fon thread-indən hər hansı UI çağırışı ya ignore edilir, ya da crash-ə səbəb olur (iOS-da — Main Thread Checker Exception, Android-də — CalledFromWrongThreadException). İstisna — Android-də SurfaceView və TextureView, burada çəkmə ayrıca thread-dən yerinə yetirilə bilər.

Müasir mobil framework-lər (SwiftUI, Jetpack Compose) bu məhdudiyyəti saxlayır: SwiftUI tələb edir ki, State və ObservedObject-də bütün dəyişikliklər Main Thread-də baş versin, baxmayaraq ki, çəkmənin özü qismən fon thread-lərinə çıxarılıb. Jetpack Compose də State-in Main Thread-də dəyişdirilməsini gözləyir. İstisna — drawBehind və layout ilə bağlı Compose modifier-ləri, aşkar sənədləşmə ilə digər thread-lərdən çağırıla bilər.

iOS-da Main Thread: RunLoop.main və DispatchQueue.main

DispatchQueue.main — iOS-da Main Thread-ə kod göndərmək üçün əsas mexanizmdir. Bu, tətbiqin əsas RunLoop-na bağlı serial növbədir. Ora göndərilən bütün bloklar ardıcıl olaraq, gəliş sırası ilə yerinə yetirilir. SwiftUI və UIKit avtomatik yenilənir, əgər siz State-i dəyişdirirsinizsə və ya setNeedsLayout()-i Main Thread-dən çağırırsınızsa. Fon tapşırığından nəticəni asinxron qaytarmaq üçün DispatchQueue.main.async {} istifadə edin.

Objective-C-Swift körpüsündə həmçinin Thread.isMainThread mövcuddur — cari kodun əsas thread-də yerinə yetirilib-yetirilmədiyini yoxlayan xüsusiyyət. Mövcud UIKit layihələri üçün bu standart pattern-dir: if Thread.isMainThread { updateUI() } else { DispatchQueue.main.async { updateUI() } }. SwiftUI-də bu yoxlama adətən tələb olunmur, çünki framework özü body və modifier-in Main Thread-də çağırılmasını təmin edir.

swift
import UIKit

class ViewController: UIViewController {

    let imageView = UIImageView()

    func loadImageFromNetwork() {
        // Fon thread-i: şəklin endirilməsi
        DispatchQueue.global(qos: .background).async { [weak self] in
            guard let url = URL(string: "https://example.com/image.png"),
                  let data = try? Data(contentsOf: url),
                  let image = UIImage(data: data)
            else { return }

            // UI yeniləməsi üçün Main Thread-ə qayıt
            DispatchQueue.main.async {
                self?.imageView.image = image
                self?.imageView.setNeedsLayout()
            }
        }
    }

    // Kodun Main Thread-də icra olunub-olunmadığını yoxlama
    func safeUpdateUI() {
        if Thread.isMainThread {
            updateUI()
        } else {
            DispatchQueue.main.async {
                self.updateUI()
            }
        }
    }

    private func updateUI() {
        print("UI Main Thread-də yeniləndi")
    }
}

loadImageFromNetwork() nümunəsi düzgün pattern-i göstərir: URLSession və ya Data(contentsOf:) DispatchQueue.global vasitəsilə fon thread-də yerinə yetirilir, sonra nəticə UIImageView-i yeniləmək üçün DispatchQueue.main-ə qaytarılır. DispatchQueue.main.async olmadan tətbiq fon thread-dən UIKit çağırarkən NSInternalInconsistencyException ilə crash olacaq.

DispatchQueue.main.async — qayıtma zəmanəti

iOS-da Main Thread-də kodun icrasının ən etibarlı yolu — açıq şəkildə DispatchQueue.main.async vasitəsilə göndərməkdir. Siz artıq Main Thread-də olsanız belə, async göndərmə problem yaratmır: GCD onu RunLoop-un növbəti iterasiyasında emal edir. Sinxron icra üçün DispatchQueue.main.sync istifadə edin, lakin bu, Main Thread-dən sync çağırarsanız deadlock-a səbəb ola bilər. Qayda: nəticənin qaytarılması üçün async, yalnız əsas thread-də olmadığınıza zəmanət varsa sync.

RunLoop.main Main Thread-in əsası kimi

RunLoop.main — iOS-un əsas hadisə növbəsi ilə əlaqəli CFRunLoop obyektidir. O, giriş mənbələrini (touch events), timer-ləri və DispatchQueue.main bloklarını emal edir. Hər çəkmə kadrı (60/120 FPS) VSync-dən əvvəl RunLoop-da bütün əməliyyatların tamamlanmasını tələb edir. Main Thread-də əməliyyatlar 16.6 ms (60 FPS) və ya 8.3 ms (120 FPS) dən çox çəkərsə, tətbiq kadrları atır, bu da vizual olaraq jank və ya stutter kimi özünü göstərir.

Android-də Main Thread: Looper və Handler

Looper.getMainLooper() — Android-də əsas thread ilə iş üçün əsas mexanizmdir. Android-də hər Main Thread Looper-a malikdir, o, növbədən (MessageQueue) sonsuz olaraq mesajları çıxarır və emal üçün Handler-ə ötürür. Activity.runOnUiThread() və View.post() — bunlar Handler(Looper.getMainLooper()) üzərində yüksək səviyyəli sarğılardır. Dispatchers.Main ilə Kotlin Coroutines — əsas thread-ə qayıtmağın müasir yoludur.

Android həmçinin StrictMode təqdim edir — Main Thread-i bloklayan əməliyyatları aşkar etmək üçün alət. StrictMode.setThreadPolicy() sizə siyasət təyin etməyə imkan verir: əsas thread-də şəbəkə çağırışlarının qadağanı (NetworkPolicy), diskdən oxuma (DiskRead), diskə yazma (DiskWrite). Siyasət pozulduqda istisna yaradılır və ya logcat-ə mesaj yazılır.

kotlin
// Android: Main Thread və Kotlin Coroutines ilə iş
import android.os.Bundle
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URL

class MainActivity : ComponentActivity() {

    private lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        textView = TextView(this)
        setContentView(textView)

        // Nümunə: asinxron məlumat yükləmə
        lifecycleScope.launch {
            val result = loadData() // Dispatchers.IO-da yerinə yetirilir
            textView.text = result // UI Main Thread-də
        }
    }

    private suspend fun loadData(): String {
        return withContext(Dispatchers.IO) {
            URL("https://api.example.com/data").readText()
        }
    }
}

// StrictMode Main Thread pozuntularını aşkar etmək üçün
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        StrictMode.setThreadPolicy(
            StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()
                .detectDiskWrites()
                .detectNetwork()
                .penaltyLog()
                .build()
        )
    }
}

Kotlin nümunəsi Dispatchers.Main düzgün istifadəsini lifecycleScope.launch və Dispatchers.IO-nu withContext vasitəsilə göstərir. Bütün şəbəkə işi IO-dispatcher-də yerinə yetirilir, TextView yeniləməsi isə avtomatik Main Thread-də, çünki lifecycleScope-də launch default olaraq Dispatchers.Main istifadə edir. Application.onCreate()-də StrictMode təsadüfi şəbəkə çağırışlarını və disk əməliyyatlarını əsas thread-də tutur.

Main Thread pozuntularının aşkarlanması

Main Thread Checker — Xcode-da quraşdırılmış alət (Xcode 9-dan mövcuddur), UIKit, AppKit və digər UI framework-lərinin fon thread-lərindən çağırışlarını tapır. Debug zamanı Main Thread Checker bütün UI-API çağırışlarını analiz edir və pozuntu aşkar edildikdə ətraflı stack trace ilə breakpoint göstərir. Real cihazlarda (release quruluşunda) Main Thread Checker işləmir — pozuntular crash və ya düzgün olmayan davranış kimi özünü göstərəcək.

Android-də analoq StrictMode (yuxarıda təsvir edilmişdir) və daxili log detektordur: fon thread-indən View.setText() və ya View.invalidate() çağırıldıqda Android CalledFromWrongThreadException atır. Əlavə olaraq Android Studio Profiler Main Thread-də hansı əməliyyatların yerinə yetirildiyini göstərir. Main Thread-də şəbəkə və ya fayl əməliyyatları görürsünüzsə — bu problemin dəqiq əlamətidir.

AlətPlatformaNəyi aşkar edir
Main Thread CheckeriOS (Xcode)Fon thread-lərindən UIKit/AppKit çağırışları
StrictModeAndroidŞəbəkə, disk, Main Thread-də uzun əməliyyatlar
Android Studio ProfilerAndroidMain Thread yükünün zamanla vizuallaşdırılması
Time ProfileriOS (Instruments)Main Thread-də metodların icra müddətinin ölçülməsi
HUD / DispatchQueue.main.asynciOSDebug vasitəsilə UI bloklanmasının vizual göstəricisi

Vizual pattern: kəsikli scroll

Main Thread bloklanmasının ən nəzərə çarpan simptomu — janky scroll (kəsikli scroll). İstifadəçi UITableView və ya RecyclerView scroll etdikdə, sistem növbəti kadrın 16 ms ərzində hazır olacağını gözləyir. Main Thread-də şəkil dekodlaşdırması və ya JSON parse icra olunursa, kadr çəkmə gecikir və istifadəçi sıçrayışlar görür. Diaqnostika üçün profiler-dən istifadə edin: əgər prepareDisplay() və ya layoutSubviews() metodu >16 ms çəkirsə — məlumatlar yanlış thread-də emal olunur.

Main Thread bloklanmasının tipik ssenariləri

Birinci ssenari — Main Thread-də URLConnection və ya Data(contentsOf:) vasitəsilə sinxron şəbəkə sorğusu. Android-də StrictMode detectNetwork() ilə dərhal bu pozuntunu tutacaq. iOS-da sinxron URLSession açıq səhv verməyəcək, lakin UI sorğu müddəti ərzində (1-10 saniyə) donacaq. Həll: asinxron callback ilə URLSession.dataTask (iOS) və ya Retrofit/OkHttp (Android) istifadə edin.

İkinci ssenari — şəkillərin dekodlaşdırılması və sıxılması. Android-də əsas thread-də UIImage(data:) və ya BitmapFactory.decodeResource() — jank-ın ən çox yayılmış səbəblərindən biridir. 4000x3000 piksel şəkil 50-150 millisaniyə dekodlaşır ki, bu da 16 ms limitini aşır. Həll: fon thread-də dekodlaşdırmaya zəmanət verən ImageLoader (Kingfisher, Coil, Glide) istifadə edin.

Üçüncü ssenari — JSON parse. Main Thread-də JSONSerialization (iOS) və ya JSONObject (Android) vasitəsilə API cavabının təhlili. Hətta 100 KB-lıq kiçik JSON 5-15 millisaniyə parse olunur, lakin yavaş cihazlarda — 50 millisaniyəyə qədər. Digər əməliyyatlarla birlikdə bu yığılır və buraxılmış kadrlara səbəb olur. Həll: fon thread-də parse() çağırışı ilə kotlinx.serialization/Decodable istifadə edin, Main Thread-də yalnız nəticənin təyin edilməsini buraxın.

Tez-tez verilən suallar

Mobil inkişafda Main Thread nədir?

Main Thread — tətbiqin əsas thread-i, onun üzərində bütün UI əməliyyatları yerinə yetirilir: toxunuşların işlənməsi, ekranın çəkilməsi, animasiyalar, layout yeniləməsi. iOS-da bu RunLoop.main və DispatchQueue.main, Android-də — Looper.getMainLooper(). Bütün UI framework-ləri (UIKit, Android Views) thread-unsafe-dir və yalnız Main Thread-dən çağırılma tələb edir. Bu thread-də hər hansı uzun əməliyyat interfeysi bloklayır.

Nə üçün UI yalnız əsas thread-də yenilənməlidir?

UI framework-ləri memarlıq baxımından performans üçün thread-unsafe-dir: blokadalar vasitəsilə giriş sinxronizasiyası hər çəkmə əməliyyatına 20-40% əlavə yük qoyardı. UIKit və Android tərtibatçıları bir thread modelini seçdilər, burada race condition mutex olmadan aradan qaldırılır. Bütün UI dəyişiklikləri ciddi şəkildə Main Thread-də yerinə yetirilməlidir — əks halda crash və ya düzgün olmayan göstərim.

Fon thread-indən nəticəni Main Thread-ə necə qaytarmaq olar?

iOS-da kod göndərmək üçün DispatchQueue.main.async { } istifadə edin. Android-də — runOnUiThread { } və ya Dispatchers.Main ilə Kotlin Coroutines. Müasir yanaşma — korutinlər: fon işi üçün withContext(Dispatchers.IO) və launch-da avtomatik Dispatchers.Main. Java layihələri üçün Handler(Looper.getMainLooper()).post { }.

ANR nədir və Main Thread ilə necə əlaqəlidir?

ANR (Application Not Responding) — Android dialoqu, Main Thread 5 saniyədən çox bloklandıqda görünür. ANR o deməkdir ki, sistem giriş hadisəsinə (toxunma, düymə basma) tətbiqdən cavab almayıb və ya BroadcastReceiver 10 saniyə ərzində tamamlanmayıb. Səbəb — Main Thread-də sinxron əməliyyat: şəbəkə sorğusu, verilənlər bazası ilə iş, mürəkkəb hesablamalar. iOS-da analoq — dialogsuz UI donması.

SwiftUI Main Thread-də icranı yoxlayırmı?

SwiftUI avtomatik olaraq body və modifier-in Main Thread-də yerinə yetirilməsini təmin edir. Lakin fon thread-indən @Published xüsusiyyətlərinin və ya State-in dəyişdirilməsi (məsələn, URLSession delegate-dən) problemlərə səbəb ola bilər. ObservableObject sinifləri üçün @MainActor istifadə edin ki, bütün metodları Main Thread-də yerinə yetirilsin. SwiftUI 5.5+-də @MainActor ObservableObject üçün avtomatik əlavə olunur.

Nəticə

  • Main Thread — UI üçün yeganə thread: toxunuşlar, çəkmə, layout, animasiyalar; bütün UI framework-ləri thread-unsafe
  • Bloklama Main Thread >5 saniyə Android-də ANR, iOS-da daxili dialoq olmadan interfeysin donmasına səbəb olur
  • DispatchQueue.main (iOS) və Dispatchers.Main / runOnUiThread (Android) — əsas thread-ə qayıtma mexanizmləri
  • Şəbəkə, JSON parse, şəkil dekodlaşdırması — ən çox səhvən Main Thread-də yerinə yetirilən əməliyyatlar
  • Main Thread Checker (Xcode) və StrictMode (Android) debug mərhələsində fon thread-lərindən UI çağırışlarını aşkar edir
  • SwiftUI Main Thread-də icra üçün @MainActor istifadə edir, Jetpack Compose — default olaraq Dispatchers.Main
  • Profiler-lər (Instruments Time Profiler, Android Studio Profiler) Main Thread yükünü göstərir və dar boğazları tapmağa kömək edir

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun