withContext — একটি করুটিনের ভিতরে এক্সিকিউশন কনটেক্স্ট পরিবর্তনকারী ফাংশন যা কোডের নির্দিষ্ট ব্লকের জন্য অস্থায়ীভাবে থ্রেড বা ডিসপ্যাচার পরিবর্তন করে এবং ফলাফল মূল কনটেক্স্টে ফিরিয়ে দেয়। JetBrains, 2025 অনুসারে, withContext নেটওয়ার্ক রিকোয়েস্ট এবং ডিস্ক অপারেশনের জন্য সবচেয়ে বেশি ব্যবহৃত করুটিন টুলগুলির মধ্যে একটি। ফাংশনটি গ্যারান্টি দেয় যে ব্লক শেষ হওয়ার পরে করুটিন মূল ডিসপ্যাচারে এক্সিকিউশন চালিয়ে যায়, যা আকস্মিক থ্রেড-সুরক্ষা ত্রুটিগুলি প্রতিরোধ করে।
মূল পয়েন্ট
withContext kotlinx.coroutines প্যাকেজ থেকে একটি সাসপেন্ডিং ফাংশন যা কোডের প্রদত্ত ব্লককে একটি নির্দিষ্ট CoroutineContext এ এক্সিকিউট করে এবং ফলাফল মূল কনটেক্সটে ফেরত দেয়। ফাংশনের সিগনেচারটি এরকম:
suspend fun withContext (
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
context প্যারামিটার যেকোনো CoroutineContext গ্রহণ করে — সাধারণত স্ট্যান্ডার্ড Dispatchers.IO, Dispatchers.Default বা Dispatchers.Main এর একটি। ব্লকটি সেই কনটেক্সটে এক্সিকিউট হয়, এবং ফলাফল where withContext কল করা হয়েছিল সেখানে ফেরত দেওয়া হয়।
ল্যাম্বডা শেষ হওয়ার পরে, withContext নিশ্চিতভাবে এক্সিকিউশন মূল ডিসপ্যাচারে ফিরিয়ে আনে। এর মানে ডেভেলপারকে ব্যাকগ্রাউন্ড অপারেশনের পরে ম্যানুয়ালি withContext(Dispatchers.Main) কল করতে হবে না — প্রত্যাবর্তন স্বয়ংক্রিয়ভাবে ঘটে। এই আচরণ Kotlin Coroutines স্পেসিফিকেশনে সংস্করণ 1.3 থেকে নথিভুক্ত।
Android ডেভেলপমেন্ট হলো withContext ব্যবহারের প্রধান ক্ষেত্র। একটি সাধারণ পরিস্থিতি: ViewModel প্রধান থ্রেডে একটি করুটিন শুরু করে, ভিতরে নেটওয়ার্ক রিকোয়েস্টের জন্য withContext(Dispatchers.IO) কল করে, এবং Main এ স্বয়ংক্রিয় প্রত্যাবর্তনের পরে ফলাফল UI আপডেট করতে ব্যবহৃত হয়। এই পদ্ধতি MVVM আর্কিটেকচারের ভিত্তি এবং Google এর অফিসিয়াল করুটিন গাইডে সুপারিশকৃত।
withContext বুঝতে, আপনাকে CoroutineContext এবং এর মূল উপাদান — ডিসপ্যাচার বুঝতে হবে। প্রতিটি করুটিনের কনটেক্স্ট এলিমেন্টের একটি সেট থাকে, যার মধ্যে ডিসপ্যাচার নির্ধারণ করে কোন থ্রেড বা থ্রেড পুলে কোড চলে।
| ডিসপ্যাচার | উদ্দেশ্য | পুল আকার |
|---|---|---|
| Dispatchers.Main | প্রধান UI থ্রেড (Android, JavaFX, Swing) | 1 (প্রধান থ্রেড) |
| Dispatchers.IO | ডিস্ক এবং নেটওয়ার্ক অপারেশন | 64 থ্রেড (সীমা বাড়ে) |
| Dispatchers.Default | CPU-নিবিড় গণনা | max(2, কোর সংখ্যা) |
| Dispatchers.Unconfined | কোনো নির্দিষ্ট থ্রেড নেই | অসীম |
এটা বোঝা গুরুত্বপূর্ণ যে withContext একটি নতুন করুটিন তৈরি করে না — এটি কেবল বিদ্যমান করুটিনের কনটেক্স্ট পরিবর্তন করে। এটি launch এবং async থেকে মূল পার্থক্য, যারা নতুন করুটিন তৈরি করে। withContext এর অভ্যন্তরীণ বাস্তবায়ন অপ্টিমাইজ করা: যদি অনুরোধকৃত কনটেক্স্ট বর্তমানের সাথে মেলে, কোনো সুইচিং ঘটে না — ফাংশনটি একই ডিসপ্যাচারে এক্সিকিউট হয়।
Dispatchers.Main withContext(Dispatchers.Main) এর ভিতরে পরিবর্তন ঘটায় না — Kotlin Coroutines কনটেক্স্টের অভিন্নতা চিনতে পারে এবং অপ্রয়োজনীয় অপারেশন এড়িয়ে যায়। একইভাবে, withContext(Dispatchers.Default) ইতিমধ্যে Default এ চলমান করুটিনের ভিতরে কোনো ওভারহেড তৈরি করে না। এই অপ্টিমাইজেশন ContinuationInterceptor এ বাস্তবায়িত।
নতুনরা প্রায়ই withContext কে launch এবং async এর সাথে গুলিয়ে ফেলে, যেহেতু তিনটি ফাংশনই করুটিন এবং কনটেক্সট নিয়ে কাজ করে। তবে তাদের উদ্দেশ্য মৌলিকভাবে ভিন্ন।
| বৈশিষ্ট্য | withContext | launch | async |
|---|---|---|---|
| নতুন করুটিন তৈরি করে | না | হ্যাঁ | হ্যাঁ |
| ফলাফল ফেরত দেয় | হ্যাঁ (T সরাসরি) | না (Job) | হ্যাঁ (Deferred<T>) |
| এক্সিকিউশন | সিকোয়েন্সিয়াল | সমান্তরাল | সমান্তরাল |
| ফলাফলের অপেক্ষা | স্বয়ংক্রিয় | join() | await() |
| সাধারণ ব্যবহার-ক্ষেত্র | ডিসপ্যাচার পরিবর্তন | ফায়ার-এন্ড-ফরগেট | সমান্তরাল গণনা |
যদি আপনার ব্যাকগ্রাউন্ড থ্রেডে একটি অপারেশন এক্সিকিউট করতে এবং ফলাফল পেতে হয় — withContext ব্যবহার করুন। যদি আপনার একাধিক স্বাধীন অপারেশন সমান্তরালে চালানোর প্রয়োজন হয় — await সহ async ব্যবহার করুন। যদি ফলাফলের প্রয়োজন না হয় (লগিং, ক্যাশ লেখা) — launch ব্যবহার করুন। Google Android আর্কিটেকচারে Repository স্তরের জন্য withContext কে পছন্দের টুল হিসেবে সুপারিশ করে।
আসুন Kotlin Android অ্যাপ্লিকেশনে withContext ব্যবহারের তিনটি ব্যবহারিক পরিস্থিতি দেখি। প্রতিটি উদাহরণ একটি নির্দিষ্ট কাজ এবং সঠিক প্যাটার্ন প্রদর্শন করে।
ViewModel Main এ করুটিন থেকে একটি রিপোজিটরি মেথড কল করে। ভিতরে, withContext(Dispatchers.IO) HTTP রিকোয়েস্ট সম্পাদন করে, এবং ফলাফল স্বয়ংক্রিয়ভাবে ফেরত আসে:
class UserRepository(
private val api: UserApi
) {
suspend fun getUser(id: String): User {
return withContext(Dispatchers.IO) {
api.fetchUser(id)
}
}
}
ViewModel এ করুটিন getUser কে যেকোনো সাধারণ সাসপেন্ডিং ফাংশনের মতো কল করে — ডিসপ্যাচার স্পষ্টভাবে উল্লেখ না করেই। withContext থ্রেড পরিবর্তনের বিবরণ লুকিয়ে রাখে।
যখন আপনি একের পর এক একাধিক IO অপারেশন করতে চান, withContext সেগুলিকে একটি ব্লকে একত্রিত করে। এটি প্রতিটি অপারেশনকে আলাদা withContext এ মোড়ানোর চেয়ে বেশি কার্যকর:
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 ব্যবহার করা ভাল।
কিছু পরিস্থিতিতে, আপনাকে এমন কোড এক্সিকিউট করতে হবে যা বাতিল করা যায় না — উদাহরণস্বরূপ, স্ক্রিন বন্ধ করার সময় অবস্থা সংরক্ষণ। withContext + NonCancellable এর সংমিশ্রণ এই কাজটি সমাধান করে:
withContext(Dispatchers.IO + NonCancellable) {
cache.saveState(state)
analytics.logEvent("state_saved")
}
অপারেটর + দুটি কনটেক্স্ট এলিমেন্ট একত্রিত করে: IO ডিসপ্যাচার এবং NonCancellable ফ্ল্যাগ। ব্লকটি এক্সিকিউট হয় এমনকি যদি প্যারেন্ট করুটিন বাতিল করা হয় — ফাইনালাইজিং অপারেশনের জন্য উপযোগী।
withContext এর অভ্যন্তরীণ বাস্তবায়ন Continuation পদ্ধতির উপর ভিত্তি করে — Kotlin করুটিনের কেন্দ্রীয় অ্যাবস্ট্রাকশন। প্রতিটি সাসপেন্ড পয়েন্ট Continuation অবজেক্টে এক্সিকিউশন অবস্থা সংরক্ষণ করে, এবং withContext তার ব্যতিক্রম নয়।
Kotlin কম্পাইলার withContext কে kotlinx.coroutines থেকে withContext মেথড কলে অনুবাদ করে, যা অভ্যন্তরীণভাবে DispatchedContinuation এর একটি নতুন ইনস্ট্যান্স তৈরি করে। এই অবজেক্টটি মূল Continuation কে মোড়ানো করে এবং তার ডিসপ্যাচার প্রতিস্থাপন করে। যদি নতুন ডিসপ্যাচার বর্তমান থেকে ভিন্ন হয়, এক্সিকিউশন স্থগিত হয়, ব্লকটি সংশ্লিষ্ট থ্রেড পুলে পাঠানো হয়, এবং সম্পূর্ণ হওয়ার পরে — মূল কনটেক্সট দিয়ে পুনরায় শুরু হয়।
যখন withContext একই ডিসপ্যাচারের সাথে কল করা হয় যাতে করুটিন ইতিমধ্যে চলছে, Kotlin ফাস্ট-পাথ (fast-path) সক্রিয় করে: ব্লকটি সিঙ্ক্রোনাসভাবে এক্সিকিউট হয়, DispatchedContinuation তৈরি না করে এবং থ্রেড পুলে না পাঠিয়ে। এটি withContext কে একই কনটেক্সট সহ পুনরাবৃত্ত কলের জন্য কার্যত বিনামূল্যে করে তোলে। JetBrains বেঞ্চমার্ক (kotlinx.coroutines 1.8) অনুসারে, ফাস্ট-পাথ 0.1 µs এর কমে সম্পন্ন হয়।
withContext এর প্রতিটি কল ভিন্ন ডিসপ্যাচারের সাথে একটি নতুন DispatchedContinuation তৈরি করে এবং থ্রেড পরিবর্তনের প্রয়োজন — এতে লোডের উপর নির্ভর করে 1 থেকে 5 µs সময় লাগে। বেশিরভাগ অ্যাপ্লিকেশনের জন্য এই বিলম্ব অলক্ষিত, তবে হাজার হাজার পুনরাবৃত্তি সহ লুপের ভিতরে, একটি withContext ব্লকে অপারেশন একত্রিত করা মূল্যবান।
অভিজ্ঞ ডেভেলপাররাও withContext নিয়ে কাজ করার সময় ভুল করেন। আসুন চারটি সবচেয়ে সাধারণ সমস্যা এবং সেগুলি প্রতিরোধের উপায় দেখি।
ডেভেলপাররা প্রায়ই প্রতিটি লাইনকে আলাদা withContext এ মোড়ান, অপারেশনগুলি একটি ব্লকে একত্রিত করার পরিবর্তে। ভিন্ন ডিসপ্যাচারের সাথে প্রতিটি অতিরিক্ত কল ওভারহেড তৈরি করে।
সঠিক: সিকোয়েন্সিয়াল IO অপারেশনগুলি একটি withContext(Dispatchers.IO) { ... } এ একত্রিত করুন। যদি কিছু অপারেশন CPU-নিবিড় হয় — একই ব্লকের ভিতরে withContext(Dispatchers.Default) ব্যবহার করুন।
withContext কোড সিকোয়েন্সিয়ালভাবে এক্সিকিউট করে। যদি দুটি স্বাধীন নেটওয়ার্ক রিকোয়েস্ট একটি withContext এ মোড়ানো হয়, তারা একের পর এক চলবে। সমান্তরালতার জন্য, async + await ব্যবহার করুন।
// সিকোয়েন্সিয়াল — ধীর
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()}")
}
যদি withContext চলাকালীন একটি করুটিন বাতিল করা হয়, Dispatchers.IO তে ব্লকটিও বাধাগ্রস্ত হয়। যে অপারেশনগুলি যেকোনো মূল্যে সম্পন্ন হতে হবে (ডেটাবেস লেখা, অ্যানালিটিক্স পাঠানো) তাদের জন্য withContext কে NonCancellable এর সাথে একত্রিত করুন।
কখনই withContext(Dispatchers.IO) এর ভিতরে View কম্পোনেন্ট আপডেট করবেন না। withContext পুরো ব্লক শেষ না হওয়া পর্যন্ত Main এ ফেরত আসে না। withContext এর ক্লোজিং ব্রেসের পরে UI আপডেট রাখুন — তখন করুটিন ইতিমধ্যে প্রধান থ্রেডে থাকবে।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
withContext একটি সাসপেন্ডিং ফাংশন যা থ্রেড ব্লক করে না, বরং বিদ্যমান করুটিনের ভিতরে কনটেক্সট পরিবর্তন করে। runBlocking করুটিন এবং সাধারণ কোডের মধ্যে একটি সেতু যা সম্পন্ন না হওয়া পর্যন্ত বর্তমান থ্রেড ব্লক করে। withContext UI থ্রেডের জন্য নিরাপদ, runBlocking নয়।
না, withContext একটি suspend ফাংশন, তাই এটি শুধুমাত্র অন্য suspend ফাংশন বা করুটিন (launch/async) থেকে কল করা যায়। সাধারণ ফাংশন থেকে withContext কল করা যায় না — এর জন্য runBlocking বা CoroutineScope প্রয়োজন।
Kotlin ফাস্ট-পাথ (fast-path) সক্রিয় করে — ব্লকটি একই থ্রেডে সিঙ্ক্রোনাসভাবে পরিবর্তন ছাড়াই এক্সিকিউট হয়। ওভারহেড 0.1 µs এর কম। এটি কোনো ত্রুটি নয়, তবে এই ধরনের কল অপ্রয়োজনীয় — withContext ছাড়াই কোড এক্সিকিউট করা ভাল।
withContext এর ভিতরে ব্যতিক্রম সাধারণ কোডের মতোই প্রচারিত হয় — try-catch এর মাধ্যমে। যদি ব্লকটি একটি ব্যতিক্রম ছুঁড়ে, এটি প্যারেন্ট করুটিনে প্রচারিত হয় এবং যদি হ্যান্ডেল না করা হয় তবে এটি বাতিল করে দেয়। withContext এর ভিতরে বা তার চারপাশে try-catch ব্যবহার করুন।
না, withContext একটি নতুন করুটিন তৈরি করে না। এটি বিদ্যমান করুটিন ব্যবহার করে তবে অস্থায়ীভাবে তার কনটেক্সট পরিবর্তন করে। এটি একে launch এবং async থেকে আলাদা করে, যারা চাইল্ড করুটিন তৈরি করে। এই আচরণ kotlinx.coroutines সোর্স কোড দ্বারা নিশ্চিত।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন