Main Thread — মোবাইল অ্যাপ্লিকেশনে নির্বাহের প্রধান থ্রেড যা সম্পূর্ণ ইউজার ইন্টারফেস পরিচালনা করে: স্পর্শ, রেন্ডারিং, লেআউট আপডেট এবং অ্যানিমেশন। iOS-এ এটি RunLoop.main, Android-এ — Looper.getMainLooper()। এই থ্রেডে যেকোনো দীর্ঘ অপারেশন UI ব্লক করে এবং ANR (Android) বা ইন্টারফেস ফ্রিজ (iOS) ঘটায়। Apple UIKit ডকুমেন্টেশন অনুযায়ী, UI ক্লাসগুলি থ্রেড-নিরাপদ নয় এবং শুধুমাত্র Main Thread থেকে কল প্রয়োজন।
মূল পয়েন্ট
Main Thread হল থ্রেড যা অ্যাপ্লিকেশন শুরু হলে অপারেটিং সিস্টেম দ্বারা তৈরি হয় এবং সমস্ত ইউজার ইন্টারফেস ইভেন্ট পরিচালনার জন্য দায়ী। মোবাইল প্ল্যাটফর্মের প্রসঙ্গে, Main Thread-কে UI Thread-ও বলা হয়, কারণ রেন্ডারিং, স্পর্শ পরিচালনা এবং অ্যানিমেশন সম্পর্কিত সমস্ত অপারেশন এতে নির্বাহিত হয়। প্রতিটি অ্যাপ্লিকেশনে ঠিক একটি Main Thread থাকে, এবং সমস্ত UI ফ্রেমওয়ার্ক (UIKit, AppKit, Android Views, Compose UI) থ্রেড-অনিরাপদ — তারা অন্যান্য থ্রেড থেকে কল করলে সঠিক অপারেশন নিশ্চিত করে না।
স্থাপত্য অনুসারে, Main Thread Event Loop প্যাটার্ন বাস্তবায়ন করে: থ্রেড নতুন ইভেন্টের (স্পর্শ, সিস্টেম বিজ্ঞপ্তি, টাইমার) জন্য অসীমভাবে অপেক্ষা করে এবং সারি ক্রমে সেগুলি প্রক্রিয়া করে। যখন একটি ইভেন্ট প্রক্রিয়া করা হচ্ছে, পরবর্তীটি সারিতে অপেক্ষা করে। যদি প্রক্রিয়াকরণে ১০০-২০০ মিলিসেকেন্ডের বেশি সময় লাগে, ব্যবহারকারী বিলম্ব (jank) লক্ষ্য করেন। যদি ৫ সেকেন্ডের (Android) বেশি — সিস্টেম ANR (Application Not Responding) ডায়ালগ দেখায় এবং অ্যাপ্লিকেশন বন্ধ করার প্রস্তাব দেয়।
Main Thread বোঝার গুরুত্ব অতিরঞ্জিত করা যায় না: এটি মোবাইল অ্যাপ্লিকেশনে ৯০% কর্মক্ষমতা সমস্যার উৎস। ডেভেলপাররা প্রায়ই ভারী অপারেশন (নেটওয়ার্ক, ফাইল, JSON পার্সিং, ইমেজ কম্প্রেশন) ব্যাকগ্রাউন্ড থ্রেডে সরাতে ভুলে যান। একটি অপারেশন যা ইমুলেটরে ১০ মিলিসেকেন্ড নেয়, তা ধীর ডিস্কযুক্ত বাস্তব ডিভাইসে ৫০০ মিলিসেকেন্ড নিতে পারে এবং লক্ষণীয় ব্যবধান তৈরি করতে পারে।
থ্রেড-অনিরাপদ UI ফ্রেমওয়ার্কগুলি UIKit (২০০৭) এবং Android (২০০৮) এর প্রথম সংস্করণে নেওয়া একটি স্থাপত্য সিদ্ধান্ত। প্রধান কারণ কর্মক্ষমতা: লকের মাধ্যমে UI উপাদানগুলিতে অ্যাক্সেস সিঙ্ক্রোনাইজ করা প্রতিটি রেন্ডারিং অপারেশনে ওভারহেড যুক্ত করবে। পরিবর্তে, ফ্রেমওয়ার্কগুলি সমস্ত UI পরিবর্তনগুলি কঠোরভাবে একটি থ্রেডে সম্পাদন করতে প্রয়োজন, যা ওভারহেড ছাড়াই রেস কন্ডিশন দূর করে।
কল্পনা করুন দুটি ব্যাকগ্রাউন্ড থ্রেড একই সাথে textView.setText() কল করছে। যদি UI থ্রেড-নিরাপদ হত, উভয় কল mutex-এর মাধ্যমে সিঙ্ক্রোনাইজ হবে, যা রেন্ডারিং ২০-৪০% ধীর করত। বর্তমান স্থাপত্যে, ব্যাকগ্রাউন্ড থ্রেড থেকে যেকোনো UI কল হয় উপেক্ষা করা হয় বা ক্র্যাশ ঘটায় (iOS-এ — Main Thread Checker Exception, Android-এ — CalledFromWrongThreadException)। ব্যতিক্রম হল Android-এ SurfaceView এবং TextureView, যেখানে রেন্ডারিং আলাদা থ্রেড থেকে সম্পাদন করা যেতে পারে।
আধুনিক মোবাইল ফ্রেমওয়ার্ক (SwiftUI, Jetpack Compose) এই সীমাবদ্ধতা বজায় রাখে: SwiftUI-এর জন্য সমস্ত State এবং ObservedObject পরিবর্তন Main Thread-এ হওয়া প্রয়োজন, যদিও রেন্ডারিং নিজেই আংশিকভাবে ব্যাকগ্রাউন্ড থ্রেডে স্থানান্তরিত হয়। Jetpack Compose-ও Main Thread-এ State পরিবর্তন আশা করে। ব্যতিক্রম হল drawBehind এবং layout-এর সাথে সম্পর্কিত Compose modifiers, যা স্পষ্টভাবে নথিভুক্ত থাকলে অন্যান্য থ্রেড থেকে কল করা যেতে পারে।
DispatchQueue.main — iOS-এ Main Thread-এ কোড পাঠানোর প্রাথমিক ব্যবস্থা। এটি অ্যাপ্লিকেশনের প্রধান RunLoop-এর সাথে যুক্ত একটি সিরিয়াল সারি। এতে পাঠানো সমস্ত ব্লক ক্রমিকভাবে, আগমনের ক্রমে নির্বাহিত হয়। আপনি Main Thread থেকে State পরিবর্তন বা setNeedsLayout() কল করলে SwiftUI এবং UIKit স্বয়ংক্রিয়ভাবে আপডেট হয়। ব্যাকগ্রাউন্ড টাস্ক থেকে ফলাফলের অ্যাসিঙ্ক্রোনাস ফিরিয়ে আনার জন্য, DispatchQueue.main.async {} ব্যবহার করুন।
Objective-C-Swift ব্রিজে, Thread.isMainThread-ও উপলব্ধ — একটি বৈশিষ্ট্য যা পরীক্ষা করে বর্তমান কোড প্রধান থ্রেডে নির্বাহিত হচ্ছে কিনা। বিদ্যমান UIKit প্রকল্পের জন্য, এটি একটি মানক প্যাটার্ন: if Thread.isMainThread { updateUI() } else { DispatchQueue.main.async { updateUI() } }। SwiftUI-তে এই পরীক্ষা সাধারণত প্রয়োজন হয় না, কারণ ফ্রেমওয়ার্ক নিশ্চিত করে যে body এবং modifier Main Thread-এ নির্বাহিত হবে।
import UIKit
class ViewController: UIViewController {
let imageView = UIImageView()
func loadImageFromNetwork() {
// ব্যাকগ্রাউন্ড থ্রেড: ছবি ডাউনলোড হচ্ছে
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 আপডেট করতে Main Thread-এ ফিরে যান
DispatchQueue.main.async {
self?.imageView.image = image
self?.imageView.setNeedsLayout()
}
}
}
// কোড Main Thread-এ নির্বাহিত হচ্ছে কিনা পরীক্ষা করুন
func safeUpdateUI() {
if Thread.isMainThread {
updateUI()
} else {
DispatchQueue.main.async {
self.updateUI()
}
}
}
private func updateUI() {
print("Main Thread-এ UI আপডেট করা হয়েছে")
}
}
উদাহরণে, loadImageFromNetwork() সঠিক প্যাটার্ন প্রদর্শন করে: URLSession বা Data(contentsOf:) DispatchQueue.global-এর মাধ্যমে ব্যাকগ্রাউন্ড থ্রেডে নির্বাহিত হয়, তারপরে ফলাফল UIImageView আপডেট করতে DispatchQueue.main-এ ফিরিয়ে আনা হয়। DispatchQueue.main.async ছাড়া, ব্যাকগ্রাউন্ড থ্রেড থেকে UIKit কল করলে অ্যাপ্লিকেশন NSInternalInconsistencyException-এর সাথে ক্র্যাশ করবে।
iOS-এ Main Thread-এ কোড নির্বাহের সবচেয়ে নির্ভরযোগ্য উপায় হল DispatchQueue.main.async-এর মাধ্যমে স্পষ্ট ডিসপ্যাচ। এমনকি যদি আপনি ইতিমধ্যে Main Thread-এ থাকেন, async ডিসপ্যাচ সমস্যা তৈরি করে না: GCD এটি পরবর্তী RunLoop পুনরাবৃত্তিতে প্রক্রিয়া করে। সিঙ্ক্রোনাস নির্বাহের জন্য, DispatchQueue.main.sync ব্যবহার করুন, কিন্তু Main Thread থেকে কল করলে এটি ডেডলক হতে পারে। নিয়ম: async ফলাফল ফিরিয়ে আনার জন্য, sync শুধুমাত্র যখন আপনি নিশ্চিত যে আপনি প্রধান থ্রেডে নেই।
RunLoop.main হল একটি CFRunLoop অবজেক্ট যা iOS-এর প্রধান ইভেন্ট সারির সাথে যুক্ত। এটি ইনপুট উৎস (স্পর্শ ইভেন্ট), টাইমার এবং DispatchQueue.main ব্লক প্রক্রিয়া করে। প্রতিটি রেন্ডারিং ফ্রেমের (৬০/১২০ FPS) জন্য প্রয়োজন যে উল্লম্ব সিঙ্ক পালস (VSync)-এর আগে RunLoop-এ সমস্ত অপারেশন সম্পন্ন হোক। যদি Main Thread-এ অপারেশনগুলি ১৬.৬ ms (৬০ FPS) বা ৮.৩ ms (১২০ FPS)-এর বেশি সময় নেয়, অ্যাপ্লিকেশন ফ্রেম হারায়, যা দৃশ্যত jank বা stutter হিসাবে প্রকাশ পায়।
Looper.getMainLooper() — প্রধান থ্রেডের সাথে কাজ করার জন্য Android-এর প্রধান ব্যবস্থা। Android-এ প্রতিটি Main Thread-এ একটি Looper থাকে যা সারি (MessageQueue) থেকে বার্তাগুলি অসীমভাবে বের করে এবং প্রক্রিয়াকরণের জন্য Handler-এ পাঠায়। Activity.runOnUiThread() এবং View.post() হল Handler(Looper.getMainLooper())-এর উচ্চ-স্তরের আবরণ। Dispatchers.Main সহ Kotlin Coroutines প্রধান থ্রেডে ফিরে আসার আধুনিক উপায়।
Android StrictMode-ও প্রদান করে — Main Thread ব্লক করে এমন অপারেশন সনাক্ত করার একটি টুল। StrictMode.setThreadPolicy() আপনাকে একটি নীতি নির্ধারণ করতে দেয়: প্রধান থ্রেডে নেটওয়ার্ক কল (NetworkPolicy), ডিস্ক রিড (DiskRead), ডিস্ক রাইট (DiskWrite) নিষিদ্ধ করা। যখন নীতি লঙ্ঘন হয়, একটি ব্যতিক্রম তৈরি হয় বা logcat-এ একটি বার্তা লেখা হয়।
// Android: Main Thread এবং Kotlin Coroutines-এর সাথে কাজ করা
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)
// উদাহরণ: অ্যাসিঙ্ক্রোনাস ডেটা লোডিং
lifecycleScope.launch {
val result = loadData() // Dispatchers.IO-তে নির্বাহিত হচ্ছে
textView.text = result // Main Thread-এ UI
}
}
private suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
URL("https://api.example.com/data").readText()
}
}
}
// Main Thread লঙ্ঘন সনাক্ত করার জন্য StrictMode
class App : Application() {
override fun onCreate() {
super.onCreate()
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
}
}
Kotlin উদাহরণ lifecycleScope.launch-এর মাধ্যমে Dispatchers.Main এবং withContext-এর মাধ্যমে Dispatchers.IO-এর সঠিক ব্যবহার দেখায়। সমস্ত নেটওয়ার্ক কাজ IO ডিসপ্যাচারে নির্বাহিত হয়, যখন TextView আপডেট স্বয়ংক্রিয়ভাবে Main Thread-এ হয়, কারণ lifecycleScope-এ launch ডিফল্টরূপে Dispatchers.Main ব্যবহার করে। Application.onCreate()-এ StrictMode প্রধান থ্রেডে আকস্মিক নেটওয়ার্ক কল এবং ডিস্ক অপারেশনগুলি আটকায়।
Main Thread Checker — Xcode-এ নির্মিত একটি টুল (Xcode ৯ থেকে উপলব্ধ) যা ব্যাকগ্রাউন্ড থ্রেড থেকে UIKit, AppKit এবং অন্যান্য UI ফ্রেমওয়ার্কে কল সনাক্ত করে। ডিবাগিংয়ের সময়, Main Thread Checker সমস্ত UI-API কল বিশ্লেষণ করে এবং লঙ্ঘন সনাক্ত করলে বিস্তারিত স্ট্যাক ট্রেস সহ একটি breakpoint দেখায়। বাস্তব ডিভাইসে (রিলিজ বিল্ডে), Main Thread Checker কাজ করে না — লঙ্ঘন ক্র্যাশ বা ভুল আচরণ হিসাবে প্রকাশ পায়।
Android-এ, সমতুল্য হল StrictMode (উপরে বর্ণিত) এবং নির্মিত লগ ডিটেক্টর: ব্যাকগ্রাউন্ড থ্রেড থেকে View.setText() বা View.invalidate() কল করলে, Android CalledFromWrongThreadException ছোঁড়ে। অতিরিক্তভাবে, Android Studio Profiler দেখায় Main Thread-এ কোন অপারেশনগুলি নির্বাহিত হচ্ছে। যদি আপনি Main Thread-এ নেটওয়ার্ক বা ফাইল অপারেশন দেখেন — এটি সমস্যার স্পষ্ট লক্ষণ।
| টুল | প্ল্যাটফর্ম | এটি কী সনাক্ত করে |
|---|---|---|
| Main Thread Checker | iOS (Xcode) | ব্যাকগ্রাউন্ড থ্রেড থেকে UIKit/AppKit কল |
| StrictMode | Android | Main Thread-এ নেটওয়ার্ক, ডিস্ক, দীর্ঘ অপারেশন |
| Android Studio Profiler | Android | সময়ের সাথে Main Thread লোডের ভিজুয়ালাইজেশন |
| Time Profiler | iOS (Instruments) | Main Thread-এ পদ্ধতি নির্বাহের সময় পরিমাপ |
| HUD / DispatchQueue.main.async | iOS | ডিবাগিংয়ের মাধ্যমে UI ব্লকিংয়ের দৃশ্য নির্দেশ |
Main Thread ব্লকিংয়ের সবচেয়ে লক্ষণীয় লক্ষণ হল জার্কি স্ক্রল (janky scroll)। যখন ব্যবহারকারী একটি UITableView বা RecyclerView স্ক্রল করেন, সিস্টেম আশা করে পরবর্তী ফ্রেম ১৬ ms-এ প্রস্তুত হবে। যদি Main Thread-এ ইমেজ ডিকোডিং বা JSON পার্সিং করা হচ্ছে, ফ্রেম রেন্ডারিং বিলম্বিত হয় এবং ব্যবহারকারী থেমে থেমে যাওয়া দেখেন। রোগনির্ণয়ের জন্য, একটি প্রোফাইলার ব্যবহার করুন: যদি prepareDisplay() বা layoutSubviews() >১৬ ms নেয় — ডেটা ভুল থ্রেডে প্রক্রিয়া করা হচ্ছে।
প্রথম পরিস্থিতি — Main Thread-এ URLConnection বা Data(contentsOf:) এর মাধ্যমে সিঙ্ক্রোনাস নেটওয়ার্ক অনুরোধ। Android-এ, detectNetwork() সহ StrictMode অবিলম্বে এই লঙ্ঘন ধরে। iOS-এ, একটি সিঙ্ক্রোনাস URLSession স্পষ্ট ত্রুটি দেয় না, কিন্তু অনুরোধের সময় UI ফ্রিজ হয় (১-১০ সেকেন্ড)। সমাধান: অ্যাসিঙ্ক্রোনাস কলব্যাক সহ URLSession.dataTask (iOS) বা Retrofit/OkHttp (Android) ব্যবহার করুন।
দ্বিতীয় পরিস্থিতি — ইমেজ ডিকোডিং এবং কম্প্রেশন। Android-এ প্রধান থ্রেডে UIImage(data:) বা BitmapFactory.decodeResource() jank-এর সবচেয়ে সাধারণ কারণগুলির মধ্যে একটি। ৪০০০x৩০০০ পিক্সেলের একটি ইমেজ ৫০-১৫০ মিলিসেকেন্ডে ডিকোড হয়, যা ১৬ ms সীমা অতিক্রম করে। সমাধান: ImageLoader (Kingfisher, Coil, Glide) ব্যবহার করুন, যা ব্যাকগ্রাউন্ড থ্রেডে ডিকোডিং নিশ্চিত করে।
তৃতীয় পরিস্থিতি — JSON পার্সিং। Main Thread-এ JSONSerialization (iOS) বা JSONObject (Android) এর মাধ্যমে API প্রতিক্রিয়া পার্স করা। এমনকি একটি ছোট ১০০ KB JSON ৫-১৫ মিলিসেকেন্ডে পার্স হয়, কিন্তু ধীর ডিভাইসে — ৫০ মিলিসেকেন্ড পর্যন্ত। অন্যান্য অপারেশনের সাথে মিলিত হয়ে, এটি জমা হয় এবং ফ্রেম ড্রপের ফল দেয়। সমাধান: kotlinx.serialization/Decodable ব্যবহার করুন যার parse() ব্যাকগ্রাউন্ড থ্রেডে কল করা হয়, শুধুমাত্র ফলাফল অ্যাসাইনমেন্ট Main Thread-এ রেখে।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
Main Thread হল অ্যাপ্লিকেশনের প্রধান থ্রেড যেখানে সমস্ত UI অপারেশন সম্পাদিত হয়: স্পর্শ পরিচালনা, স্ক্রিন রেন্ডারিং, অ্যানিমেশন, লেআউট আপডেট। iOS-এ এটি RunLoop.main এবং DispatchQueue.main, Android-এ — Looper.getMainLooper()। সমস্ত UI ফ্রেমওয়ার্ক (UIKit, Android Views) থ্রেড-অনিরাপদ এবং শুধুমাত্র Main Thread থেকে কল প্রয়োজন। এই থ্রেডে যেকোনো দীর্ঘ অপারেশন ইন্টারফেস ব্লক করে।
UI ফ্রেমওয়ার্কগুলি কর্মক্ষমতার জন্য স্থাপত্য অনুসারে থ্রেড-অনিরাপদ: লকের মাধ্যমে অ্যাক্সেস সিঙ্ক্রোনাইজ করা প্রতিটি রেন্ডারিং অপারেশনে ২০-৪০% ওভারহেড যুক্ত করবে। UIKit এবং Android ডেভেলপাররা একক-থ্রেড মডেল বেছে নিয়েছেন যেখানে mutex ছাড়াই রেস কন্ডিশন দূর হয়। সমস্ত UI পরিবর্তন কঠোরভাবে Main Thread-এ সম্পাদন করা উচিত — অন্যথায় ক্র্যাশ বা ভুল প্রদর্শন।
iOS-এ প্রধান সারিতে কোড পাঠাতে DispatchQueue.main.async { } ব্যবহার করুন। Android-এ — runOnUiThread { } বা Dispatchers.Main সহ Kotlin Coroutines। আধুনিক পদ্ধতি হল coroutines: ব্যাকগ্রাউন্ড কাজের জন্য withContext(Dispatchers.IO) এবং launch-এ স্বয়ংক্রিয় Dispatchers.Main। Java প্রকল্পের জন্য, Handler(Looper.getMainLooper()).post { }।
ANR (Application Not Responding) একটি Android ডায়ালগ যা দেখা যায় যদি Main Thread ৫ সেকেন্ডের বেশি ব্লক থাকে। ANR মানে সিস্টেম অ্যাপ্লিকেশন থেকে ইনপুট ইভেন্টে (স্পর্শ, কী প্রেস) সাড়া পায়নি বা BroadcastReceiver ১০ সেকেন্ডে সম্পূর্ণ হয়নি। কারণ Main Thread-এ একটি সিঙ্ক্রোনাস অপারেশন: নেটওয়ার্ক অনুরোধ, ডাটাবেস কাজ, জটিল গণনা। iOS-এ, সমতুল্য হল ডায়ালগ ছাড়া UI ফ্রিজ।
SwiftUI স্বয়ংক্রিয়ভাবে নিশ্চিত করে যে body এবং modifier Main Thread-এ নির্বাহিত হবে। তবে, ব্যাকগ্রাউন্ড থ্রেড থেকে @Published বৈশিষ্ট্য বা State-এ পরিবর্তন (উদাহরণস্বরূপ, URLSession ডেলিগেট থেকে) সমস্যা তৈরি করতে পারে। ObservableObject ক্লাসের জন্য @MainActor ব্যবহার করুন যাতে তাদের সমস্ত পদ্ধতি Main Thread-এ নির্বাহিত হয়। SwiftUI ৫.৫+-এ, @MainActor ObservableObject-এর জন্য স্বয়ংক্রিয়ভাবে যুক্ত হয়।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন