Valkarn Tasks क्यों चुनें
Unity के लिए शून्य-आवंटन async/await। UniTask से तेज़। Awaitable से स्मार्ट।
समस्या: Unity में async टूटी हुई है
Unity डेवलपर्स को हर जगह असंक्रामक ऑपरेशनों की जरूरत होती है: सीन लोड करना, एसेट डाउनलोड करना, एनिमेशन का इंतजार करना, स्पॉन में देरी करना, सर्वर से संचार करना। आज, विकल्प हैं:
| विकल्प | समस्या |
|---|---|
| Coroutines | कोई रिटर्न वैल्यू नहीं, कोई एरर हैंडलिंग नहीं, कोई कैंसलेशन नहीं, कोई कम्पोजीशन नहीं |
| System.Threading.Tasks | हर async कॉल पर आवंटन (~144–232 बाइट्स), GC ट्रिगर करता है, Unity लाइफसाइकिल की कोई जागरूकता नहीं |
| UniTask | अच्छा — लेकिन: 18 मिनट के बाद टोकन टकराव, कम्पाइल-टाइम डायग्नोस्टिक्स नहीं, फिनलाइज़र-निर्भर एरर रिपोर्टिंग |
| Unity Awaitable (2023+) | क्लास-आधारित (आवंटन करता है), कोई कम्बाइनर नहीं, कोई चैनल नहीं, कोई टेस्टिंग सपोर्ट नहीं |
Valkarn Tasks इन सभी समस्याओं को एक ही सोर्स-जेनरेटेड, शून्य-आवंटन पैकेज में हल करता है।
शून्य आवंटन — हर बाइट क्यों मायने रखती है
गेम्स के लिए आवंटन का अर्थ
हर new object(), new List<T>(), या async Task कॉल मैनेजड हीप पर आवंटन करती है, जिसे Garbage Collector ट्रैक करता है। Unity Boehm GC उपयोग करता है जिसमें दो गंभीर समस्याएं हैं:
- Stop-the-world पॉज़ — जब GC चलता है, आपका गेम फ्रीज हो जाता है। 60 fps पर 2 ms का पॉज़ आपके फ्रेम बजट का 12% खाता है; 120 fps (VR) पर 24%।
- अप्रत्याशित टाइमिंग — GC बॉस फाइट, कटसीन, या कॉम्पीटिटिव मैच के दौरान ट्रिगर हो सकता है।
Valkarn Tasks क्या अलग करता है
| परिदृश्य | System.Task | UniTask | Valkarn Tasks |
|---|---|---|---|
async Method() — सिंक्रोनसली पूर्ण | 144 बाइट्स | 0 बाइट्स | 0 बाइट्स |
async Method() — एक बार सस्पेंड | 232+ बाइट्स | 0 बाइट्स (pooled) | 0 बाइट्स (pooled) |
WhenAll(a, b) | 232 बाइट्स | 0 बाइट्स (pooled) | 0 बाइट्स (source-gen pooled) |
WhenAny(a, b) | 144 बाइट्स | 72 बाइट्स | 0 बाइट्स |
| Promise (मैन्युअल पूर्णता) | 144 बाइट्स | 104 बाइट्स | 88 बाइट्स |
| Pooled Promise (पुन: उपयोगी) | 144 बाइट्स | 0 बाइट्स | 0 बाइट्स |
50–100 async ऑपरेशनों के साथ एक सामान्य गेम फ्रेम में, System.Task 7–23 KB कचरा उत्पन्न करता है। Valkarn Tasks शून्य उत्पन्न करता है।
इसका आपके गेम पर क्या प्रभाव पड़ता है
- कोई GC स्टटरिंग नहीं — async ऑपरेशनों से बिना रुकावट लॉक्ड फ्रेमरेट
- VR-सुरक्षित — GC स्पाइक्स के बिना 90/120 fps लक्ष्य
- मोबाइल-फ्रेंडली — सीमित RAM डिवाइस पर कम मेमोरी प्रेशर
- कंसोल-सर्टिफाइड — पूर्वानुमेय मेमोरी व्यवहार सर्टिफिकेशन आवश्यकताओं को पास करने में मदद करता है
प्रदर्शन — सर्वश्रेष्ठ के मुकाबले बेंचमार्क
बेंचमार्क: BenchmarkDotNet v0.14.0, .NET 9.0, Intel Core i7-10875H।
Core async/await — ValueTask से 2× तेज़
| बेंचमार्क | ValueTask | UniTask | Valkarn Tasks | vs ValueTask |
|---|---|---|---|---|
| 100 टास्क बल्क | 956 ns | 508 ns | 489 ns | 1.95× |
| 1,000 टास्क बल्क | 9,697 ns | 5,016 ns | 4,728 ns | 2.05× |
| CancellationToken के साथ | 38.8 ns | 36.2 ns | 29.6 ns | 1.31× |
| एक्सेप्शन हैंडलिंग | 10,399 ns | 8,247 ns | 9,248 ns | 1.12× |
सभी पाथ: 0 बाइट्स आवंटित।
कम्बाइनर — Task से 9.6× तक तेज़
| बेंचमार्क | Task | UniTask | Valkarn Tasks | vs Task |
|---|---|---|---|---|
| WhenAll (2 टास्क) | 117 ns / 232B | 13.6 ns / 0B | 12.1 ns / 0B | 9.6× |
| WhenAll (5 टास्क) | 156 ns / 272B | 25.1 ns / 0B | 25.3 ns / 0B | 6.2× |
| WhenAny (2 टास्क) | 39.0 ns / 144B | 60.0 ns / 72B | 11.6 ns / 0B | 3.4× |
| Pooled Promise | 59.5 ns / 144B | 53.6 ns / 0B | 38.3 ns / 0B | 1.55× |
WhenAny UniTask से 5.2× तेज़ है और शून्य बाइट्स आवंटित करता है।
ऑब्जेक्ट पूल — 4.3 नैनोसेकंड
| ऑपरेशन | समय | आवंटन |
|---|---|---|
| मेन-थ्रेड फास्ट-स्लॉट रेंट + रिटर्न | 4.3 ns | 0 बाइट्स |
| क्रॉस-थ्रेड Treiber स्टैक | ~15 ns | 0 बाइट्स |
मेन थ्रेड पर शून्य एटॉमिक ऑपरेशन — IL2CPP के लिए महत्वपूर्ण जहां Volatile.Read 9.2× धीमा है।
वास्तविक दुनिया का प्रभाव (60 fps पर 50 async ops/फ्रेम)
| लाइब्रेरी | समय/फ्रेम | फ्रेम बजट | GC/सेकंड |
|---|---|---|---|
| System.Task | ~48 µs | 0.29% | ~430 KB/s |
| UniTask | ~25 µs | 0.15% | ~3.6 KB/s |
| Valkarn Tasks | ~24 µs | 0.14% | 0 KB/s |
10 मिनट में, System.Task ~258 MB async कचरा उत्पन्न करता है। Valkarn Tasks शून्य उत्पन्न करता है।
वे फीचर्स जो कोई अन्य लाइब्रेरी नहीं देती
स्वचालित लाइफसाइकिल कैंसलेशन
public partial class EnemySpawner : MonoBehaviour
{
async ValkarnTask Start()
{
while (true)
{
await ValkarnTask.Delay(3000);
SpawnWave();
}
// जब यह GameObject नष्ट होता है तो स्वचालित रूप से कैंसल।
// कोई CancellationToken नहीं। कोई मेमोरी लीक नहीं। कोई ज़ॉम्बी टास्क नहीं।
}
}
नष्ट होने के बाद चलने वाली async मेथड्स से MissingReferenceException अब नहीं। कोई मैन्युअल OnDestroy क्लीनअप नहीं। कोई भूला हुआ CancellationTokenSource डिस्पोज़ल नहीं।
क्रिटिकल सेक्शन
async ValkarnTask SaveProgress()
{
var data = await LoadPlayerData(); // कैंसल योग्य
await using (ValkarnTask.Critical())
{
await db.Insert(data); // GO नष्ट होने पर भी पूर्ण होता है
await db.Commit();
} // पेंडिंग कैंसलेशन अब लागू होता है
await SendNotification(); // फिर से कैंसल योग्य
}
डेटाबेस राइट्स, नेटवर्क रिक्वेस्ट और फाइल सेव्स तब भी पूर्ण होते हैं जब प्लेयर छोड़ देता है या सीन अनलोड होती है। कोई करप्ट सेव्स नहीं। कोई आधी लिखी analytics नहीं। कोई खोई हुई रसीदें नहीं।
कम्पाइल-टाइम डायग्नोस्टिक्स
| डायग्नोस्टिक | यह क्या पकड़ता है |
|---|---|
| TT001 | ValkarnTask का डबल-अवेट (use-after-free बग) |
| TT002 | टास्क को अवेट करना भूलना (साइलेंट फेल्योर) |
| TT012 | कैंसलेशन चेक के बिना async लूप (ज़ॉम्बी लूप) |
| TT013 | रिटर्न किया लेकिन अवेट नहीं किया टास्क (fire-and-forget बग) |
| TT016 | बिना await के async मेथड (अनावश्यक ओवरहेड) |
| TT017 | ValkarnTask<T> पर [FireAndForget] (एक परिणाम को नज़रअंदाज़ करना) |
IDE में लाल अंडरलाइन के रूप में पकड़े गए बग — परीक्षण के 20 मिनट बाद रनटाइम क्रैश नहीं।
Result<T> — try/catch के बिना एरर हैंडलिंग
var result = await loadTask.AsResult();
if (result.Succeeded) Use(result.Value);
else if (result.IsCanceled) ShowRetryDialog();
else Debug.LogError(result.Error);
हर एरर पाथ स्पष्ट है। कोई निगली हुई exceptions नहीं। कोई मिसिंग हैंडलर नहीं।
Backpressure के साथ चैनल
var channel = ValkarnTask.Channel.CreateBounded<EnemySpawnRequest>(capacity: 10);
// प्रोड्यूसर (गेम लॉजिक)
await channel.Writer.WriteAsync(new EnemySpawnRequest { type = EnemyType.Dragon });
// कंज़्यूमर (स्पॉन सिस्टम)
await foreach (var request in channel.Reader.ReadAllAsync())
await SpawnEnemy(request);
सिस्टम को साफ़ तरीके से अलग करें। स्पॉनिंग को रेट-लिमिट करें। नेटवर्क मैसेज कतारबद्ध करें। इनपुट इवेंट बफर करें। जब कंज़्यूमर नहीं रख सकता तो प्रोड्यूसर धीमा हो जाता है — मेमोरी स्पाइक्स को रोकता है।
TestClock के साथ डिटर्मिनिस्टिक टेस्टिंग
[Test]
public void Respawn_WaitsThreeSeconds()
{
var clock = new TestClock();
var task = spawner.RespawnEnemy();
clock.Advance(TimeSpan.FromSeconds(2));
Assert.IsFalse(task.IsCompleted);
clock.Advance(TimeSpan.FromSeconds(1));
Assert.IsTrue(task.IsCompleted);
}
टाइम-डिपेंडेंट लॉजिक तुरंत टेस्ट करें। टेस्ट में yield return new WaitForSeconds(3) नहीं। कोई फ्लेकी CI टाइमिंग नहीं।
जेनरेशनल टोकन सेफ्टी
UniTask एक short टोकन (16-बिट) उपयोग करता है। 65,536 पूल साइकिल (~18 मिनट सक्रिय async काम) के बाद, एक पुराना संदर्भ चुपके से दूसरे टास्क का परिणाम पढ़ता है — एक use-after-free बग जिसे reproduce करना लगभग असंभव है।
Valkarn Tasks प्रति पूल स्लॉट एक uint जेनरेशन काउंटर उपयोग करता है: टकराव से पहले 4,294,967,296 साइकिल प्रति स्लॉट। किसी भी वास्तविक परिदृश्य में असंभव।
माइग्रेशन मिनटों में, हफ्तों में नहीं
UniTask से
चरण 1: Valkarn Tasks इंस्टॉल करें
चरण 2: IDE में UniTask उपयोग पर पीले बल्ब दिखते हैं
चरण 3: राइट-क्लिक → "Fix all occurrences in Solution" (Ctrl+.)
चरण 4: UniTask पैकेज संदर्भ हटाएं
15 माइग्रेशन डायग्नोस्टिक्स (MIG001–MIG015) हर UniTask API को स्वचालित रूप से कवर करते हैं। 500–2,000 async मेथड वाला एक सामान्य प्रोजेक्ट 5 मिनट से कम में माइग्रेट होता है। 95%+ पूरी तरह स्वचालित।
Unity Awaitable से
वही एक-क्लिक माइग्रेशन:
async Awaitable→async ValkarnTaskAwaitable.NextFrameAsync()→ValkarnTask.NextFrame()Awaitable.BackgroundThreadAsync()→ValkarnTask.SwitchToThreadPool()Awaitable.MainThreadAsync()→ हटाया गया (Valkarn डिफ़ॉल्ट रूप से मेन थ्रेड पर चलता है)
तुलना मैट्रिक्स
| फीचर | System.Task | UniTask | Awaitable | Valkarn Tasks |
|---|---|---|---|---|
| शून्य-आवंटन सिंक पाथ | नहीं | हाँ | नहीं | हाँ |
| शून्य-आवंटन कम्बाइनर | नहीं | नहीं | नहीं | हाँ (source gen) |
| Struct-आधारित | नहीं | हाँ | नहीं | हाँ |
| लाइफसाइकिल ऑटो-कैंसल | नहीं | मैन्युअल | आंशिक | स्वचालित |
| कोई सिब्लिंग कैंसलेशन नहीं | नहीं | नहीं | नहीं | हाँ |
| क्रिटिकल सेक्शन | नहीं | नहीं | नहीं | हाँ |
| Result<T> (थ्रो नहीं) | नहीं | आंशिक | नहीं | हाँ |
| TestClock | नहीं | नहीं | नहीं | हाँ |
| Job System ब्रिज | नहीं | नहीं | नहीं | हाँ |
| कम्पाइल-टाइम डायग्नोस्टिक्स | नहीं | नहीं | नहीं | हाँ (17 नियम) |
| बाउंडेड पूल + ट्रिम | नहीं | नहीं | लागू नहीं | हाँ |
| डिटर्मिनिस्टिक एरर रिपोर्ट | नहीं | नहीं (finalizer) | आंशिक | हाँ (pool return) |
| पूर्ण चैनल | हाँ (.NET) | न्यूनतम | नहीं | हाँ |
| Awaitable ब्रिज | लागू नहीं | न्यूनतम | नेटिव | पारदर्शी |
| IL2CPP-ऑप्टिमाइज़्ड पूलिंग | नहीं | नहीं (हर op पर Volatile) | लागू नहीं | हाँ (शून्य atomics) |
| टोकन टकराव सेफ्टी | लागू नहीं | 18 मिनट (short) | लागू नहीं | कभी नहीं (uint gen) |
| UniTask से ऑटो-माइग्रेशन | लागू नहीं | लागू नहीं | लागू नहीं | हाँ (15 फिक्स) |
| Awaitable से ऑटो-माइग्रेशन | लागू नहीं | लागू नहीं | लागू नहीं | हाँ (8 फिक्स) |
आपके गेम को ऐसी async चाहिए जो न रुके।