C# में समवर्तीता: अपने प्रोग्राम को बोर होने से कैसे रोकें
\n\nकल्पना करें कि आप एक शेफ हैं। आपके पास तीन बर्नर, एक ओवन और एक मिक्सर है। यदि आप सब कुछ क्रमिक रूप से करेंगे - पहले प्याज काटें, फिर भूनें, फिर क्रीम फेंटें - तो दोपहर का भोजन हमेशा के लिए खिंच जाएगा। ओवन को चालू करना कहीं अधिक कुशल है जब आप सब्जियां काट रहे हों, और क्रीम को फेंटना जब मांस पक रहा हो। यही समवर्तीता है - एक साथ कई कार्यों को करने की क्षमता, उनके बीच इस तरह स्विच करना कि संसाधन बेकार न रहें।
\n\nC# में इसके लिए तीन मुख्य उपकरण हैं: क्लासिकल थ्रेड्स (Thread), आधुनिक कार्य (Task) async/await के साथ, और इटरेटर्स के माध्यम से कार्यान्वित गोरूटीन्स की अवधारणा। इस लेख में हम प्रत्येक दृष्टिकोण का विश्लेषण करेंगे, उनके प्रदर्शन की तुलना करेंगे और दिखाएंगे कि कब क्या उपयोग करना है। यदि आप C# में लिखते हैं और चाहते हैं कि आपका कोड उड़े, रेंगे नहीं - तो आप सही जगह पर हैं।
\n\nथ्रेड्स (Thread): भारी तोपखाना
\n\nथ्रेड समवर्तीता को व्यवस्थित करने का सबसे निम्न-स्तरीय तरीका है। प्रत्येक थ्रेड आपके प्रोग्राम के अंदर एक अलग "हल्की प्रक्रिया" है, जिसका अपना स्टैक और संदर्भ होता है। C# में थ्रेड बनाना बहुत आसान है:
\n\nusing System;\nusing System.Threading;\n\nclass Program\n{\n static void Main()\n {\n Thread thread = new Thread(DoWork);\n thread.Start();\n Console.WriteLine("मुख्य थ्रेड काम कर रहा है");\n thread.Join(); // समाप्ति की प्रतीक्षा करें\n }\n\n static void DoWork()\n {\n for (int i = 0; i < 5; i++)\n {\n Console.WriteLine($"थ्रेड: {i}");\n Thread.Sleep(200);\n }\n }\n}\n\nयहां हमने एक अलग थ्रेड बनाया है जो पांच तक गिनता है, जबकि मुख्य थ्रेड एक संदेश प्रिंट करता है। Join के बिना, प्रोग्राम पांच तक पहुंचने से पहले समाप्त हो सकता था।
थ्रेड्स के नुकसान
\n- \n
- भारी वजन। प्रत्येक थ्रेड स्टैक के लिए लगभग 1 MB मेमोरी आरक्षित करता है। 1000 थ्रेड बनाने का मतलब है बिना किसी कारण के 1 GB पर कब्जा करना। \n
- सिंक्रोनाइज़ेशन की जटिलता। दो थ्रेड्स को साझा डेटा खराब करने से रोकने के लिए lock, mutex और semaphore की आवश्यकता होती है। एक गलती - और आपको deadlock या race condition मिलता है। \n
- संदर्भ स्विचिंग। ऑपरेटिंग सिस्टम थ्रेड्स के बीच स्विच करने में समय बर्बाद करता है। यदि CPU कोर की तुलना में अधिक थ्रेड हैं, तो यह एक अड़चन बन जाता है। \n
थ्रेड तब अच्छे होते हैं जब आपको भारी गणनाओं के लिए मल्टी-कोर प्रोसेसर पर वास्तविक समानता की आवश्यकता होती है। लेकिन इनपुट-आउटपुट (फ़ाइलों, नेटवर्क, डेटाबेस के साथ काम) के लिए वे अत्यधिक हैं।
\n\nTask और async/await: आधुनिक मानक
\n\n.NET 4.5 से शुरू करके, Microsoft ने एक सुरुचिपूर्ण विकल्प प्रस्तावित किया - Task Parallel Library (TPL) और कीवर्ड async/await। ये शुद्ध थ्रेड नहीं हैं, बल्कि वादे (promises) हैं: आप कंपाइलर से कहते हैं "इस काम को शुरू करो, और जब यह समाप्त हो जाए - परिणाम वापस करो"। जब काम चल रहा होता है, थ्रेड ब्लॉक नहीं होता, बल्कि पूल में वापस आ जाता है।
using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nclass Program\n{\n static async Task Main()\n {\n Console.WriteLine("डाउनलोड शुरू कर रहे हैं...");\n string data = await DownloadDataAsync();\n Console.WriteLine($"डाउनलोड किया गया: {data.Length} वर्ण");\n }\n\n static async Task<string> DownloadDataAsync()\n {\n using HttpClient client = new HttpClient();\n string result = await client.GetStringAsync("https://example.com");\n return result;\n }\n}\n\nध्यान दें: await थ्रेड को ब्लॉक नहीं करता। जब डेटा नेटवर्क पर डाउनलोड हो रहा होता है, थ्रेड पूल में वापस आ जाता है और अन्य अनुरोधों को संभाल सकता है। यह वेब सर्वर और GUI अनुप्रयोगों के लिए आदर्श है - इंटरफ़ेस हैंग नहीं होता।
यह हुड के नीचे कैसे काम करता है?
\nजब कंपाइलर async देखता है, तो वह विधि को एक स्टेट मशीन में बदल देता है। प्रत्येक await पर, विधि अपनी स्थिति को "याद" रखती है