एसिंक्रोनस कॉल में सिंक्रोनस कोड लपेटना


95

मेरे पास ASP.NET एप्लिकेशन में एक विधि है, जिसे पूरा करने में काफी समय लगता है। कैश की स्थिति और उपयोगकर्ता द्वारा प्रदान किए जाने वाले मापदंडों के आधार पर, इस पद्धति का कॉल एक उपयोगकर्ता के अनुरोध के दौरान 3 बार तक हो सकता है। प्रत्येक कॉल को पूरा होने में लगभग 1-2 सेकंड लगते हैं। विधि स्वयं सेवा के लिए तुल्यकालिक कॉल है और कार्यान्वयन को ओवरराइड करने की कोई संभावना नहीं है।
तो सेवा के लिए तुल्यकालिक कॉल कुछ इस तरह दिखता है:

public OutputModel Calculate(InputModel input)
{
    // do some stuff
    return Service.LongRunningCall(input);
}

और विधि का उपयोग है (ध्यान दें, विधि की कॉल एक से अधिक बार हो सकती है):

private void MakeRequest()
{
    // a lot of other stuff: preparing requests, sending/processing other requests, etc.
    var myOutput = Calculate(myInput);
    // stuff again
}

मैंने इस पद्धति का एक साथ काम प्रदान करने के लिए अपनी ओर से कार्यान्वयन को बदलने की कोशिश की, और यहां तक ​​कि मैं अब तक आया हूं।

public async Task<OutputModel> CalculateAsync(InputModel input)
{
    return await Task.Run(() =>
    {
        return Calculate(input);
    });
}

उपयोग (सेवा करने के लिए कॉल के साथ "अन्य सामान करें" कोड का हिस्सा):

private async Task MakeRequest()
{
    // do some stuff
    var task = CalculateAsync(myInput);
    // do other stuff
    var myOutput = await task;
    // some more stuff
}

मेरा प्रश्न निम्नलिखित है। क्या मैं ASP.NET अनुप्रयोग में निष्पादन को गति देने के लिए सही दृष्टिकोण का उपयोग करता हूं या क्या मैं अनावश्यक रूप से सिंक्रोनस कोड चलाने की कोशिश कर रहा हूँ? क्या कोई समझा सकता है कि ASP.NET में दूसरा दृष्टिकोण एक विकल्प क्यों नहीं है (यदि यह वास्तव में नहीं है)? इसके अलावा, अगर ऐसा तरीका लागू होता है, तो क्या मुझे इस तरह की विधि को अतुल्य रूप से कॉल करने की आवश्यकता है यदि यह एकमात्र कॉल है जो हम इस समय कर सकते हैं (मेरे पास ऐसा मामला है, जब कोई अन्य सामान पूरा होने के इंतजार के दौरान क्या करना है)?
इस विषय पर नेट के अधिकांश लेख async-awaitकोड के साथ दृष्टिकोण का उपयोग करते हुए कवर करते हैं , जो पहले से ही awaitableतरीके प्रदान करता है, लेकिन यह मेरा मामला नहीं है। यहाँमेरे मामले का वर्णन करने वाला अच्छा लेख है, जो समानांतर कॉल की स्थिति का वर्णन नहीं करता है, सिंक कॉल को लपेटने के विकल्प को कम करता है, लेकिन मेरी राय में मेरी स्थिति बिल्कुल ऐसा करने का अवसर है।
मदद और सुझावों के लिए अग्रिम धन्यवाद।

जवाबों:


116

दो अलग-अलग प्रकार की संगामियों के बीच अंतर करना महत्वपूर्ण है। एसिंक्रोनस कंसिस्टेंसी तब होती है जब आपके पास फ़्लाइट में कई एसिंक्रोनस ऑपरेशन होते हैं (और चूंकि प्रत्येक ऑपरेशन एसिंक्रोनस होता है, उनमें से कोई भी वास्तव में एक थ्रेड का उपयोग नहीं करता है )। समानांतर कंसीडर तब होता है जब आपके पास एक अलग ऑपरेशन करने वाले कई थ्रेड होते हैं।

इस धारणा का पुनर्मूल्यांकन करने वाली पहली बात:

विधि स्वयं सेवा के लिए तुल्यकालिक कॉल है और कार्यान्वयन को ओवरराइड करने की कोई संभावना नहीं है।

यदि आपकी "सेवा" एक वेब सेवा या कुछ और है जो I / O- बाउंड है, तो सबसे अच्छा उपाय है कि आप इसके लिए एक एसिंक्रोनस API लिखें।

मैं इस धारणा के साथ आगे बढ़ूंगा कि आपकी "सेवा" एक सीपीयू-बाउंड ऑपरेशन है जिसे वेब सर्वर के समान मशीन पर निष्पादित किया जाना चाहिए।

अगर ऐसा है, तो मूल्यांकन करने के लिए अगली बात एक और धारणा है:

मुझे तेजी से निष्पादित करने के लिए अनुरोध की आवश्यकता है।

क्या आप पूरी तरह से आश्वस्त हैं कि आपको क्या करने की आवश्यकता है? क्या इसके बजाय आपके द्वारा किए जा रहे कोई भी फ्रंट-एंड परिवर्तन हैं - उदाहरण के लिए, अनुरोध शुरू करें और उपयोगकर्ता को प्रसंस्करण के दौरान अन्य काम करने की अनुमति दें?

मैं इस धारणा के साथ आगे बढ़ूंगा कि हां, आपको वास्तव में व्यक्तिगत अनुरोध को तेजी से निष्पादित करने की आवश्यकता है।

इस स्थिति में, आपको अपने वेब सर्वर पर समानांतर कोड निष्पादित करना होगा। यह निश्चित रूप से सामान्य रूप से अनुशंसित नहीं है क्योंकि समानांतर कोड उन थ्रेड्स का उपयोग करेगा जो ASP.NET को अन्य अनुरोधों को संभालने की आवश्यकता हो सकती है, और थ्रेड्स को हटाने / जोड़ने से यह ASP.NET थ्रेडपूल हेयुरिस्टिक्स को फेंक देगा। तो, इस निर्णय का आपके पूरे सर्वर पर प्रभाव पड़ता है।

जब आप ASP.NET पर समानांतर कोड का उपयोग करते हैं, तो आप वास्तव में अपने वेब ऐप की स्केलेबिलिटी को सीमित करने का निर्णय ले रहे हैं। आपको थ्रेड मंथन की उचित मात्रा भी दिखाई दे सकती है, खासकर यदि आपके अनुरोध बिल्कुल भी फट गए हों। मैं केवल ASP.NET पर समानांतर कोड का उपयोग करने की सलाह देता हूं यदि आप जानते हैं कि एक साथ उपयोगकर्ताओं की संख्या काफी कम होगी (यानी, सार्वजनिक सर्वर नहीं)।

इसलिए, यदि आप इसे दूर करते हैं, और आप सुनिश्चित हैं कि आप ASP.NET पर समानांतर प्रसंस्करण करना चाहते हैं, तो आपके पास कुछ विकल्प हैं।

आसान तरीकों में से एक का उपयोग करना है Task.Run, जो आपके मौजूदा कोड के समान है। हालांकि, मैं एक CalculateAsyncविधि को लागू करने की अनुशंसा नहीं करता हूं क्योंकि इसका मतलब है कि प्रसंस्करण अतुल्यकालिक है (जो यह नहीं है)। इसके बजाय, Task.Runकॉल के बिंदु पर उपयोग करें:

private async Task MakeRequest()
{
  // do some stuff
  var task = Task.Run(() => Calculate(myInput));
  // do other stuff
  var myOutput = await task;
  // some more stuff
}

वैकल्पिक रूप से, अगर यह अपने कोड के साथ अच्छी तरह से काम करता है, तो आप उपयोग कर सकते हैं Parallelप्रकार, यानी, Parallel.For, Parallel.ForEach, या Parallel.InvokeParallelकोड का लाभ यह है कि अनुरोध थ्रेड को समानांतर थ्रेड्स में से एक के रूप में उपयोग किया जाता है, और फिर थ्रेड संदर्भ में निष्पादन शुरू होता है ( asyncउदाहरण से कम संदर्भ स्विचिंग है ):

private void MakeRequest()
{
  Parallel.Invoke(() => Calculate(myInput1),
      () => Calculate(myInput2),
      () => Calculate(myInput3));
}

मैं ASP.NET पर समानांतर LINQ (PLINQ) का उपयोग करने की सलाह नहीं देता।


1
विस्तृत उत्तर के लिए बहुत बहुत धन्यवाद। एक और बात मैं स्पष्ट करना चाहता हूं। जब मैंने निष्पादन का उपयोग शुरू किया Task.Run, तो सामग्री दूसरे धागे में चली गई, जो कि थ्रेड पूल से ली गई है। तब अवरुद्ध कॉल (जैसे मेरा कॉल टू सर्विस) को Runएस में लपेटने की कोई आवश्यकता नहीं है , क्योंकि वे हमेशा एक-एक धागे का उपभोग करेंगे, जो विधि के निष्पादन के दौरान अवरुद्ध हो जाएगा। ऐसी स्थिति में एकमात्र लाभ जो async-awaitमेरे मामले से बचा हुआ है, एक समय में कई क्रियाएं कर रहा है। मुझे सही जवाब दो अगर मैं गलत हूँ।
एलाड

2
हां, आपको Run(या Parallel) से मिलने वाला एकमात्र लाभ संगामिति है। संचालन अभी भी प्रत्येक एक थ्रेड को रोक रहा है। चूंकि आप कहते हैं कि सेवा एक वेब सेवा है, तो मैं उपयोग करने की सलाह नहीं देता Runया Parallel; इसके बजाय, सेवा के लिए एक अतुल्यकालिक एपीआई लिखें।
स्टीफन क्लीयर

3
@StephenCleary। आपका क्या मतलब है "... और चूंकि प्रत्येक ऑपरेशन अतुल्यकालिक है, उनमें से कोई भी वास्तव में एक धागे का उपयोग नहीं कर रहा है ..." धन्यवाद!
alltej

3
@alltej: वास्तव में अतुल्यकालिक कोड के लिए, कोई धागा नहीं है
स्टीफन क्लीयर

4
@ जोशुआफ्रैंक: सीधे तौर पर नहीं। एक तुल्यकालिक एपीआई के पीछे कोड को परिभाषा के आधार पर कॉलिंग थ्रेड को ब्लॉक करना होगा। आप एक अतुल्यकालिक एपीआई का उपयोग कर सकते हैं BeginGetResponseऔर उनमें से कई को शुरू कर सकते हैं , और फिर (समकालिक रूप से) तब तक ब्लॉक कर सकते हैं जब तक कि वे सभी पूर्ण न हो जाएं, लेकिन इस तरह का दृष्टिकोण मुश्किल है - ब्लॉग देखें । -async-code.html और msdn.microsoft.com/en-us/magazine/mt238404.aspxasyncयदि संभव हो तो सभी तरह से इसे अपनाने के लिए आमतौर पर आसान और क्लीनर है।
स्टीफन क्लीयर

1

मैंने पाया कि निम्न कोड एक टास्क को हमेशा एसिंक्रोनस रूप से चलाने के लिए परिवर्तित कर सकता है

private static async Task<T> ForceAsync<T>(Func<Task<T>> func)
{
    await Task.Yield();
    return await func();
}

और मैंने इसे निम्नलिखित तरीके से उपयोग किया है

await ForceAsync(() => AsyncTaskWithNoAwaits())

यह असिंक्रोनस रूप से किसी भी टास्क को निष्पादित करेगा ताकि आप उन्हें व्हॉट्सएप, व्हेनएयर के परिदृश्यों और अन्य उपयोगों में संयोजित कर सकें।

आप बस अपने बुलाया कोड की पहली पंक्ति के रूप में Task.Yield () भी जोड़ सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.