C# में सिंक्रोनस कोड से एसिंक्रोनस कॉल: दृष्टिकोणों की लड़ाई

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

C# में सिंक्रोनस से एसिंक्रोनस विधि को कैसे कॉल करें? सारगर्भित, बेकार बात नहीं

\n\n

कल्पना करो: तुम शाम को कोड पर बैठे हो, C# में एक पुरानी कंसोल उपयोगिता को ठीक कर रहे हो। सब कुछ सिंक्रोनस रूप से काम कर रहा है, घड़ी की तरह। और फिर — धम! — तुम्हें एक API को हिट करने की ज़रूरत है, जो केवल async Task के माध्यम से बनाया गया है। क्या पूरे प्रोग्राम को फिर से लिखना है? परिचित दर्द, है ना?

\n\n

प्रश्न «How to call asynchronous method from synchronous method in C#» Stack Overflow पर सबसे लोकप्रिय में से एक है। और यह व्यर्थ नहीं है। वहाँ, टिप्पणियों में, «.Result करो और चिंता मत करो» से लेकर «क्या तुम पागल हो, तुम्हें डेडलॉक होगा!» तक ढेरों सलाह हैं। आइए इसे स्पष्ट रूप से समझते हैं: कौन से दृष्टिकोण वास्तव में काम करते हैं और कौन से नरक का रास्ता हैं।

\n\n

सिर्फ «await» काम क्यों नहीं करता?

\n\n

अगर तुम लिखने की कोशिश करोगे:

\n
public void SyncMethod()\n{\n    await DoSomethingAsync(); // त्रुटि CS4032\n}
\n

कंपाइलर इसे कंपाइल करने से मना कर देगा। await का उपयोग केवल async संशोधक से चिह्नित विधि के अंदर ही किया जा सकता है। और async void — यह एक अलग कहानी है, जिसे हम भी समझेंगे।

\n\n

तो, हमारे पास एक एसिंक्रोनस विधि है:

\n
public async Task<string> GetDataAsync()\n{\n    using var httpClient = new HttpClient();\n    return await httpClient.GetStringAsync("https://api.example.com/data");\n}
\n\n

और एक सिंक्रोनस विधि है, जिसमें से हम इसे कॉल करना चाहते हैं। आइए विकल्पों पर चलते हैं।

\n\n

दृष्टिकोण 1: GetAwaiter().GetResult() — हैकर का आकर्षण

\n\n

सबसे लोकप्रिय और, शायद, सबसे सुरुचिपूर्ण तरीका:

\n
public string CallSync()\n{\n    return GetDataAsync().GetAwaiter().GetResult();\n}
\n\n

यह क्यों अच्छा है:

\n
    \n
  • AggregateException अपवाद उत्पन्न नहीं करता (.Result के विपरीत)। यदि एसिंक्रोनस विधि के अंदर कोई त्रुटि होती है, तो तुम्हें उसका मूल प्रकार मिलेगा, न कि कोई रैपर।
  • \n
  • कोड सामान्य रूप से पढ़ा जाता है, बिना अनावश्यक try-catch के।
  • \n
\n\n

लेकिन एक बारीकी है: यदि तुम्हारी सिंक्रोनस विधि SynchronizationContext वाले संदर्भ में निष्पादित होती है (उदाहरण के लिए, WinForms या WPF के UI थ्रेड में), तो तुम्हें डेडलॉक — थ्रेड्स का पारस्परिक अवरोध — होने का जोखिम है। थ्रेड A एसिंक्रोनस कार्य के पूरा होने की प्रतीक्षा कर रहा है, और कार्य पूरा नहीं हो सकता क्योंकि वह थ्रेड A के मुक्त होने की प्रतीक्षा कर रहा है। बकवास।

\n\n

कैसे बचें: एसिंक्रोनस विधि में ही .ConfigureAwait(false) का उपयोग करें। लेकिन यह पहले से ही API को बदल रहा है, न कि केवल कॉल करने वाले कोड को।

\n\n

दृष्टिकोण 2: Task.Run(async () => await ...).GetAwaiter() — थ्रेड पूल में निकासी

\n\n

यदि तुम एसिंक्रोनस विधि को बदल नहीं सकते, लेकिन डेडलॉक से गारंटीकृत बचना चाहते हो — तो कार्य को थ्रेड पूल में ले जाओ:

\n
public string CallSyncSafe()\n{\n    return Task.Run(async () => await GetDataAsync()).GetAwaiter().GetResult();\n}
\n\n

जादू: Task.Run डेलिगेट को ThreadPool के एक थ्रेड पर चलाता है, जिसमें SynchronizationContext नहीं होता। डेडलॉक असंभव है। भले ही मूल विधि ConfigureAwait(true) का उपयोग करती हो।

\n\n

नुकसान: एक अतिरिक्त संदर्भ स्विच (context switch)। सैकड़ों कॉल के लिए — ठीक नहीं। एक बार के लिए — आदर्श।

\n\n

दृष्टिकोण 3: .Result या .Wait() — दादी का तरीका

\n\n
public string CallSyncOldSchool()\n{\n    return GetDataAsync().Result; // या .Wait()\n}
\n\n

क्या बुरा है:

\n
    \n
  • त्रुटि पर AggregateException फेंकता है। वास्तविक त्रुटि तक पहुँचने के लिए, InnerException में खोदना पड़ता है।
  • \n
  • UI संदर्भ में डेडलॉक का वही जोखिम।
  • \n
  • कोड 2012 के क्रच जैसा दिखता है।
  • \n
\n\n

यह दृष्टिकोण कोड-रिव्यू के लिए लाल झंडा है। अगर मैं पुल-रिक्वेस्ट में ऐसा देखूँगा — तो रिजेक्ट कर दूँगा।

\n\n

दृष्टिकोण 4: AsyncHelper या कस्टम RunSync

\n\n

अनुभवी डेवलपर्स कभी-कभी अपने हेल्पर लिखते हैं। उदाहरण के लिए, ऐसा:

\n
                

ब्लॉग

पुस्तक अनुशंसाएँ