Auto-Cancel Lifecycle Binding
Unity async code में सबसे common bugs में से एक है MonoBehaviour से एक task launch करना और फिर object destroy होने पर उसे cancel करना भूल जाना। Task चलती रहती है, destroyed Unity objects तक पहुँचने की कोशिश करती है, और MissingReferenceException throw करती है — या worse, silently state corrupt करती है।
ValkarnTasks एक Roslyn source generator के माध्यम से इस class के bug को eliminate करता है जो automatically async methods को object के destroy lifetime से wire करता है।
समस्या
किसी भी infrastructure के बिना, MonoBehaviour में हर async method के लिए developer को manually CancellationToken thread करना होता है:
// Manual approach — भूलना आसान, maintain करना tedious
public class EnemyAI : MonoBehaviour
{
CancellationTokenSource _cts;
void OnEnable()
{
_cts = new CancellationTokenSource();
RunPatrolLoop(_cts.Token).Forget();
}
void OnDestroy()
{
_cts?.Cancel();
_cts?.Dispose();
}
async ValkarnTask RunPatrolLoop(CancellationToken ct)
{
while (true)
{
await ValkarnTask.Delay(2000, ct);
MoveToNextWaypoint();
}
}
}
जैसे-जैसे async methods की संख्या बढ़ती है, boilerplate भी बढ़ता है। OnDestroy skip करना — या गलत order में dispose करना — ऊपर described leaks का कारण बनता है।
Generated Approach
अपनी class को partial declare करें और ValkarnTasks बाकी काम करेगा:
// After — partial declare करें और generator wiring करेगा
public partial class EnemyAI : MonoBehaviour
{
void OnEnable()
{
RunPatrolLoop().Forget();
}
async ValkarnTask RunPatrolLoop()
{
while (true)
{
await ValkarnTask.Delay(2000, __ValkarnTaskLifetimeToken);
MoveToNextWaypoint();
}
}
}
कोई CancellationTokenSource नहीं, कोई OnDestroy नहीं, कोई disposal नहीं। Generated token automatically cancel हो जाता है जब Unity object को destroy करता है।
Source Generator कैसे काम करता है
Generator (LifecycleBindingGenerator) एक incremental Roslyn generator है जो compile time पर run होता है। इसकी pipeline के तीन stages हैं।
Stage 1 — Syntax filter
Generator आपके project में हर class declaration examine करता है। एक class candidate मानी जाती है यदि:
- यह
partialkeyword के साथ declare की गई है। - इसमें एक base class list है (यानी, यह किसी चीज़ से inherit करती है)।
यह filter purely syntactic है और बहुत fast है। इस stage पर कोई semantic analysis नहीं होती।
Stage 2 — Semantic transformation
प्रत्येक candidate class के लिए, generator Roslyn semantic model का उपयोग करता है:
- Confirm करें कि class
UnityEngine.MonoBehaviourसे derive करती है (पूरी inheritance chain walk करता है)। - सभी members enumerate करें। प्रत्येक member के लिए, check करें कि क्या यह है:
- एक
asyncmethod। UnaPartidaMas.Valkarn.Tasks.ValkarnTask(याValkarnTask<T>) return करती है।[NoAutoCancel]carry नहीं करती।
- एक
- यदि कोई qualifying methods नहीं मिली, class silently skip हो जाती है — कुछ generated नहीं होता।
- केवल पहली partial declaration process होती है। यदि कोई class multiple files में split है, generator एक बार code emit करता है, first declaration से tied, duplicate members से बचने के लिए।
Stage 3 — Code emission
Stage 1 और 2 pass करने वाली प्रत्येक class के लिए, generator एक नई .g.cs file write करता है। Game.Enemies namespace में EnemyAI नाम की class के लिए generated code इस तरह दिखता है:
// <auto-generated/>
#nullable disable
using System.Threading;
namespace Game.Enemies
{
partial class EnemyAI
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
CancellationTokenSource __valkarnTaskLifetimeCts;
/// <summary>
/// Cancellation token जो इस MonoBehaviour के destroy होने पर triggered होता है।
/// ValkarnTask source generator द्वारा auto-generated।
/// </summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
protected CancellationToken __ValkarnTaskLifetimeToken
{
get
{
if (__valkarnTaskLifetimeCts == null)
__valkarnTaskLifetimeCts =
CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken);
return __valkarnTaskLifetimeCts.Token;
}
}
}
}
मुख्य details:
CancellationTokenSourcelazy है — केवल पहली बार__ValkarnTaskLifetimeTokenaccess होने पर allocate होता है।- यह Unity के built-in
destroyCancellationToken(MonoBehaviour.destroyCancellationToken, Unity 2022 से available) से linked है। जब Unity object destroy करता है,destroyCancellationTokenfire होता है, जो__valkarnTaskLifetimeCtsतक cascade होता है, जो__ValkarnTaskLifetimeTokencancel करता है। - Field और property दोनों
EditorBrowsable(Never)mark हैं ताकि वे class के users के लिए IntelliSense pollute न करें। - Property
protectedहै, ताकि subclasses भी same token का उपयोग कर सकें।
[NoAutoCancel] Attribute
किसी भी async ValkarnTask method पर [NoAutoCancel] apply करें जब आप intentionally चाहते हैं कि यह object के lifetime से परे run करता रहे। Common scenarios:
- एक method जो disk पर data save करती है और triggering object के destroy होने पर भी complete होनी चाहिए।
- एक method जो एक अलग system द्वारा owned shared resource manage करती है।
- Transition effects जो intentionally उस object को outlive करती हैं जिसने उन्हें start किया।
public partial class SaveManager : MonoBehaviour
{
// यह method destroy पर auto-cancelled होगी
async ValkarnTask AutoSaveLoop()
{
while (true)
{
await ValkarnTask.Delay(30_000, __ValkarnTaskLifetimeToken);
await WriteSaveToDisk();
}
}
// यह method auto-cancelled नहीं होगी — इसे writing finish करनी है
[NoAutoCancel]
async ValkarnTask WriteSaveToDisk(CancellationToken ct = default)
{
await FileSystem.WriteAsync(_saveData, ct);
}
}
[NoAutoCancel] एक method-level attribute है। Generator simply उस method को अपनी qualifying method count से exclude करता है। यदि class की सभी methods [NoAutoCancel] carry करती हैं, generator उस class के लिए कुछ emit नहीं करता।
Analyzer: TT014 — CancellationToken parameter के बिना NoAutoCancel
एक companion analyzer (NoAutoCancelAnalyzer) diagnostic TT014 report करता है जब आप [NoAutoCancel] को ऐसी method पर apply करते हैं जिसमें कोई CancellationToken parameter नहीं है। यदि कोई token parameter नहीं है, method के पास cancellation observe करने का कोई तरीका नहीं है — यानी [NoAutoCancel] present है लेकिन कोई practical effect नहीं है। इसका सामान्यतः मतलब है कि आप token add करना भूल गए:
// TT014: [NoAutoCancel] applied लेकिन method में कोई CancellationToken parameter नहीं
[NoAutoCancel]
async ValkarnTask WriteSaveToDisk() // <-- ct parameter missing
{
await FileSystem.WriteAsync(_saveData);
}
एक CancellationToken parameter add करके ठीक करें:
[NoAutoCancel]
async ValkarnTask WriteSaveToDisk(CancellationToken ct = default)
{
await FileSystem.WriteAsync(_saveData, ct);
}
[FireAndForget] Attribute
[FireAndForget] एक separate, complementary attribute है जो एक async method को intentionally awaited नहीं के रूप में mark करता है। यह दो purposes serve करता है:
- Warnings suppress करता है VTASKS-TASK002 और VTASKS-TASK013, जो तब fire होते हैं जब callers
ValkarnTaskreturn valueawaitनहीं करते। - Intent signal करता है — code के future readers जानते हैं कि discard deliberate है।
Source generator [FireAndForget] methods को wrap करता है यह सुनिश्चित करने के लिए कि कोई भी unobserved exceptions ValkarnTasks के unobserved-exception handler के माध्यम से published हों बजाय silently lost होने के।
public partial class SpawnManager : MonoBehaviour
{
void OnPlayerDied()
{
// कोई warning नहीं, intent clear है
ShowDeathScreenAsync();
}
[FireAndForget]
async ValkarnTask ShowDeathScreenAsync()
{
await ValkarnTask.Delay(500, __ValkarnTaskLifetimeToken);
_deathScreen.SetActive(true);
}
}
[FireAndForget] और [NoAutoCancel] independent हैं और combine किए जा सकते हैं:
[FireAndForget]
[NoAutoCancel]
async ValkarnTask PlayGlobalMusicAsync(CancellationToken ct = default) { ... }
Analyzer: TT010 — Auto-Cancel Active
AutoCancelInfoAnalyzer हर async ValkarnTask method पर एक informational diagnostic TT010 report करता है जो MonoBehaviour में auto-cancelled होगा (यानी [NoAutoCancel] नहीं है)। यह error या warning नहीं है — यह intentional transparency है ताकि developers एक नज़र में देख सकें कि कौन से methods lifecycle-bound हैं।
आप TT010 को per-method [NoAutoCancel] के साथ suppress कर सकते हैं, या project-wide .editorconfig के माध्यम से disable कर सकते हैं यदि आप इसे देखना prefer नहीं करते।
सीमाएँ
Class को partial declare किया जाना चाहिए। Source generator non-partial class में members add नहीं कर सकता। यदि आपका MonoBehaviour partial नहीं है, generator silently इसे skip करता है और कोई binding create नहीं होती। [NoAutoCancel] और [FireAndForget] attributes अभी भी documentation और analyzers के लिए काम करती हैं, लेकिन __ValkarnTaskLifetimeToken available नहीं होगा।
Nested classes. यदि एक MonoBehaviour किसी अन्य class के अंदर nested class के रूप में declare की गई है, तो outer और inner class declarations दोनों को partial होना चाहिए। Roslyn के लिए generated members correctly compile करने के लिए सभी enclosing types को partial होना आवश्यक है।
Base classes. Generated __ValkarnTaskLifetimeToken property protected है। Subclasses automatically इसका access inherit करती हैं। Generator hierarchy में प्रत्येक class के लिए independently run करता है; यदि base class और derived class दोनों async methods के साथ partial MonoBehaviours हैं, तो प्रत्येक को अपना generated partial मिलता है, लेकिन वे same underlying token share करते हैं क्योंकि destroyCancellationToken MonoBehaviour base से inherited है।
Multiple inheritance. C# classes की multiple inheritance support नहीं करता। एक MonoBehaviour का केवल एक class base हो सकता है, इसलिए कौन सा destroyCancellationToken link करना है इसमें कोई ambiguity नहीं है।
ScriptableObjects. Generator currently केवल MonoBehaviour target करता है। ScriptableObject में Unity API में destroyCancellationToken equivalent नहीं है, इसलिए उनके लिए auto-cancel generation available नहीं है।