HttpClient.GetAsync (…) कभी भी वेट / एसिंक्स का उपयोग करते समय वापस नहीं आती है


315

संपादित करें: यह प्रश्न दिखता है कि यह एक ही समस्या हो सकती है, लेकिन इसकी कोई प्रतिक्रिया नहीं है ...

संपादित करें: परीक्षण के मामले में 5 कार्य WaitingForActivationराज्य में अटके हुए प्रतीत होते हैं ।

मैंने .NET 4.5 में System.Net.Http.HttpClient का उपयोग करते हुए कुछ विषम व्यवहार का सामना किया है - जहां (जैसे) कॉल के परिणाम की "प्रतीक्षा" httpClient.GetAsync(...)कभी वापस नहीं आएगी।

यह केवल कुछ विशेष परिस्थितियों में होता है जब नए एसिंक्स / वेट लैंग्वेज फंक्शनलिटी और टास्क एपीआई का उपयोग किया जाता है - इन निरंतरता का उपयोग करते समय कोड हमेशा काम करता है।

यहाँ कुछ कोड है जो समस्या को पुन: पेश करता है - इसे निम्नलिखित GET समापन बिंदुओं को उजागर करने के लिए Visual Studio 11 में एक नए "MVC 4 WebApi प्रोजेक्ट" में छोड़ दें:

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6

यहां प्रत्येक समापन बिंदु एक ही डेटा (स्टैकओवरफ़्लो डॉट कॉम से प्रतिक्रिया हेडर) लौटाता है, /api/test5जिसके अलावा कभी पूरा नहीं होता है।

क्या मुझे HttpClient वर्ग में बग का सामना करना पड़ा है, या क्या मैं किसी तरह एपीआई का दुरुपयोग कर रहा हूं?

पुन: पेश करने के लिए कोड:

public class BaseApiController : ApiController
{
    /// <summary>
    /// Retrieves data using continuations
    /// </summary>
    protected Task<string> Continuations_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
    }

    /// <summary>
    /// Retrieves data using async/await
    /// </summary>
    protected async Task<string> AsyncAwait_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return result.Content.Headers.ToString();
    }
}

public class Test1Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await Continuations_GetSomeDataAsync();

        return data;
    }
}

public class Test2Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = Continuations_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test3Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return Continuations_GetSomeDataAsync();
    }
}

public class Test4Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await AsyncAwait_GetSomeDataAsync();

        return data;
    }
}

public class Test5Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = AsyncAwait_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test6Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return AsyncAwait_GetSomeDataAsync();
    }
}

2
यह एक ही मुद्दा प्रतीत नहीं होता है, लेकिन यह सुनिश्चित करने के लिए कि आप इसके बारे में जानते हैं, बीटा WRT async विधियों में एक MVC4 बग है जो समकालिक रूप से पूरा होता है - देखें stackoverflow.com/questions/9627329/…
जेम्स मैनर्स

धन्यवाद - मैं उस के लिए बाहर देखता हूँ। इस मामले में मुझे लगता है कि कॉल करने के लिए विधि हमेशा अतुल्यकालिक होनी चाहिए HttpClient.GetAsync(...)?
बेंजामिन फॉक्स

जवाबों:


468

आप एपीआई का दुरुपयोग कर रहे हैं।

यहां स्थिति है: ASP.NET में, केवल एक धागा एक समय में एक अनुरोध को संभाल सकता है। यदि आवश्यक हो तो आप कुछ समानांतर प्रसंस्करण कर सकते हैं (थ्रेड पूल से अतिरिक्त थ्रेड उधार ले सकते हैं), लेकिन केवल एक थ्रेड में अनुरोध संदर्भ होगा (अतिरिक्त थ्रेड्स में अनुरोध संदर्भ नहीं है)।

यह ASP.NET द्वारा प्रबंधित किया जाता हैSynchronizationContext

डिफ़ॉल्ट रूप से, जब आप awaitएक होते हैं Task, तो विधि एक कैप्चर की गई SynchronizationContext(या एक कैप्चर की जाती है TaskScheduler, यदि कोई नहीं है SynchronizationContext)। आम तौर पर, यह वही है जो आप चाहते हैं: एक अतुल्यकालिक नियंत्रक कार्रवाई awaitकुछ होगी , और जब यह फिर से शुरू होता है, तो यह अनुरोध संदर्भ के साथ फिर से शुरू होता है।

तो, यहाँ क्यों test5विफल रहता है:

  • Test5Controller.Getनिष्पादित AsyncAwait_GetSomeDataAsync(ASP.NET अनुरोध संदर्भ के भीतर)।
  • AsyncAwait_GetSomeDataAsyncनिष्पादित HttpClient.GetAsync(ASP.NET अनुरोध संदर्भ के भीतर)।
  • HTTP अनुरोध बाहर भेजा जाता है, और HttpClient.GetAsyncएक अपूर्ण रिटर्न देता है Task
  • AsyncAwait_GetSomeDataAsyncइंतजार कर रहा है Task; चूंकि यह पूर्ण नहीं है, AsyncAwait_GetSomeDataAsyncएक अपूर्ण रिटर्न देता है Task
  • Test5Controller.Get Taskपूरा होने तक वर्तमान थ्रेड को ब्लॉक करता है ।
  • HTTP प्रतिसाद आता है, और इसके Taskद्वारा HttpClient.GetAsyncपूरा किया गया है।
  • AsyncAwait_GetSomeDataAsyncASP.NET अनुरोध संदर्भ के भीतर फिर से शुरू करने का प्रयास। हालांकि, उस संदर्भ में एक थ्रेड पहले से ही है: थ्रेड ब्लॉक इन Test5Controller.Get
  • गतिरोध।

यहाँ क्यों अन्य काम करते हैं:

  • ( test1, test2और, test3): ASP.NET अनुरोध संदर्भ के बाहरContinuations_GetSomeDataAsync थ्रेड पूल की निरंतरता को शेड्यूल करता है। यह अनुरोध के संदर्भ में फिर से प्रवेश किए बिना पूर्ण करके लौटा देता है।TaskContinuations_GetSomeDataAsync
  • ( test4और test6): के बाद से Taskहै प्रतीक्षित , ASP.NET अनुरोध धागा अवरुद्ध नहीं कर रहा है। यह AsyncAwait_GetSomeDataAsyncASP.NET अनुरोध संदर्भ का उपयोग करने की अनुमति देता है जब यह जारी रखने के लिए तैयार होता है।

और यहाँ सर्वोत्तम अभ्यास हैं:

  1. अपनी "लाइब्रेरी" asyncविधियों में, ConfigureAwait(false)जब भी संभव हो उपयोग करें । आपके मामले में, यह परिवर्तन AsyncAwait_GetSomeDataAsyncहोगाvar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. Taskएस पर ब्लॉक मत करो ; यह asyncसब नीचे है। दूसरे शब्दों में, के awaitबजाय का उपयोग करें GetResult( Task.Resultऔर Task.Waitसाथ ही प्रतिस्थापित किया जाना चाहिए await)।

इस तरह, आपको दोनों लाभ मिलते हैं: निरंतरता ( AsyncAwait_GetSomeDataAsyncविधि का शेष ) एक मूल थ्रेड पूल थ्रेड पर चलाया जाता है जिसमें ASP.NET अनुरोध संदर्भ दर्ज नहीं करना पड़ता है; और नियंत्रक स्वयं है async(जो अनुरोध थ्रेड को ब्लॉक नहीं करता है)।

अधिक जानकारी:

2012-07-13 अपडेट करें: इस उत्तर को ब्लॉग पोस्ट में शामिल करें ।


2
क्या ASP.NET के लिए कुछ दस्तावेज SynchroniztaionContextहैं जो बताते हैं कि कुछ अनुरोध के संदर्भ में केवल एक धागा हो सकता है? यदि नहीं, तो मुझे लगता है कि होना चाहिए।
svick

8
यह AFAIK कहीं भी प्रलेखित नहीं है।
स्टीफन क्लीयर

10
साभार - जबरदस्त प्रतिक्रिया कार्यात्मक रूप से समान कोड के बीच व्यवहार में अंतर निराशाजनक है लेकिन आपके स्पष्टीकरण से समझ में आता है। यह उपयोगी होगा यदि रूपरेखा ऐसे गतिरोधों का पता लगाने में सक्षम थी और कहीं एक अपवाद को बढ़ाती थी।
बेंजामिन फॉक्स

3
वहाँ स्थितियों का उपयोग कर रहे हैं। एक asp.net संदर्भ में .ConfigureAwait (गलत) की सिफारिश नहीं है? यह मुझे प्रतीत होगा कि इसका हमेशा उपयोग किया जाना चाहिए और यह केवल एक यूआई के संदर्भ में है कि इसका उपयोग नहीं किया जाना चाहिए क्योंकि आपको यूआई को सिंक करने की आवश्यकता है। या मैं बिंदु याद कर रहा हूँ?
एलेक्सगैड

3
ASP.NET SynchronizationContextकुछ महत्वपूर्ण कार्यक्षमता प्रदान करता है: यह अनुरोध संदर्भ को प्रवाहित करता है। इसमें ऑथेंटिकेशन से लेकर कुकीज से लेकर कल्चर तक सभी तरह का सामान शामिल है। इसलिए ASP.NET में, UI पर वापस सिंक करने के बजाय, आप अनुरोध संदर्भ में वापस सिंक करते हैं। यह शीघ्र ही बदल सकता है: नए में संपत्ति के रूप में एक संदर्भ ApiControllerहोता है HttpRequestMessage- इसलिए संदर्भ को प्रवाह करने की आवश्यकता नहीं हो सकती हैSynchronizationContext - लेकिन मुझे अभी तक पता नहीं है।
स्टीफन क्लीयर

61

संपादित करें: आम तौर पर गतिरोध से बचने के लिए अंतिम खाई प्रयास को छोड़कर नीचे करने से बचने की कोशिश करें। स्टीफन क्लीरी की पहली टिप्पणी पढ़ें।

यहाँ से जल्दी ठीक करो । लिखने के बजाय:

Task tsk = AsyncOperation();
tsk.Wait();

प्रयत्न:

Task.Run(() => AsyncOperation()).Wait();

या यदि आपको परिणाम की आवश्यकता है:

var result = Task.Run(() => AsyncOperation()).Result;

स्रोत से (उपरोक्त उदाहरण से मेल खाने के लिए):

AsyncOperation अब थ्रेडपूल पर लागू किया जाएगा, जहां एक SynchronizationContext नहीं होगा, और AsyncOperation के अंदर उपयोग की जाने वाली निरंतरताओं को वापस थ्रेडिंग के लिए मजबूर नहीं किया जाएगा।

मेरे लिए यह एक प्रयोग करने योग्य विकल्प की तरह लग रहा है क्योंकि मेरे पास इसे सभी तरह से उपयोग करने का विकल्प नहीं है (जो मैं पसंद करूंगा)।

स्रोत से:

सुनिश्चित करें कि FooAsync विधि में प्रतीक्षा से मार्शल को वापस संदर्भ नहीं मिलता है। ऐसा करने का सबसे सरल तरीका थ्रेडपूल से अतुल्यकालिक काम को आमंत्रित करना है, जैसे कि टास्क में मंगलाचरण लपेटकर।

int सिंक () {रिटर्न टास्क.रून () => Library.FooAsync ()) परिणाम। }

FooAsync को अब थ्रेडपूल पर लागू किया जाएगा, जहां एक SynchronizationContext नहीं होगा, और FooAsync के अंदर उपयोग की जाने वाली निरंतरता उस सिंक () को वापस थ्रेड करने के लिए मजबूर नहीं होगी।


7
अपने स्रोत लिंक को फिर से पढ़ना चाहते हैं; लेखक ऐसा करने की सलाह देता है । क्या यह काम करता है? हां, लेकिन केवल इस अर्थ में कि आप गतिरोध से बचते हैं। यह समाधान ASP.NET पर कोड के सभी लाभों की उपेक्षा करता है async, और वास्तव में पैमाने पर समस्याएं पैदा कर सकता है। BTW, ConfigureAwaitकिसी भी परिदृश्य में "उचित async व्यवहार को तोड़ नहीं"; यह बिल्कुल वही है जो आपको लाइब्रेरी कोड में उपयोग करना चाहिए।
स्टीफन क्लीयर

2
यह पूरा पहला खंड है, जिसका शीर्षक बोल्ड है Avoid Exposing Synchronous Wrappers for Asynchronous Implementationsयदि आप पूरी तरह से करने की आवश्यकता है, तो पोस्ट के पूरे बाकी कुछ अलग तरीके बता रहे हैं।
स्टीफन क्लीयर

1
स्रोत में मुझे मिला अनुभाग जोड़ा गया है - मैं इसे भविष्य के पाठकों तक तय करने के लिए छोड़ दूँगा। ध्यान दें कि आपको आम तौर पर ऐसा करने से बचने की कोशिश करनी चाहिए और इसे केवल एक अंतिम खाई के रूप में करना चाहिए (अर्थात जब async कोड का उपयोग करने पर आपका नियंत्रण नहीं है)।
योकोक

3
मुझे यहां और हमेशा की तरह सभी उत्तर पसंद हैं .... वे सभी संदर्भ पर आधारित हैं (दंडित इरादा योग्य)। मैं एक तुल्यकालिक संस्करण के साथ HttpClient के Async कॉल को लपेट रहा हूं ताकि मैं उस लाइब्रेरी में ConfigureAwait को जोड़ने के लिए उस कोड को बदल न सकूं। इसलिए उत्पादन में गतिरोध को रोकने के लिए, मैं एक Async कॉल टास्क में लपेट रहा हूं। इसलिए जैसा कि मैं इसे समझता हूं, यह प्रति अनुरोध 1 अतिरिक्त थ्रेड का उपयोग करने जा रहा है और गतिरोध से बचा जाता है। मुझे लगता है कि पूरी तरह से आज्ञाकारी होने के लिए, मुझे WebClient के सिंक तरीकों का उपयोग करने की आवश्यकता है। यह सही साबित करने के लिए बहुत काम है इसलिए मुझे अपने वर्तमान दृष्टिकोण के साथ नहीं रहने के लिए एक आकर्षक कारण की आवश्यकता होगी।
samneric

1
मैंने Async को Sync में बदलने के लिए एक Extension Method तैयार किया। मैंने यहाँ कहीं पर इसका उसी तरह से पढ़ा है। नेट फ्रेमवर्क यह करता है: सार्वजनिक स्थैतिक TResult RunSync <TResult> (यह फ़नक <टास्क <TResult >> func) {रिटर्न _taskFactory .StartNew (func) .Unwrap () .GetAwaiter ()। .GetResult (); }
समरनीक

10

आप उपयोग कर रहे के बाद से .Resultया .Waitया awaitयह एक के कारण खत्म हो जाएगा गतिरोध अपने कोड में।

आप गतिरोध को रोकने के लिए विधियों ConfigureAwait(false)में उपयोग कर सकते हैंasync

इस तरह:

var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
                             .ConfigureAwait(false);

ConfigureAwait(false)जहाँ भी संभव हो आप ब्लॉक असिंक कोड के लिए उपयोग नहीं कर सकते हैं ।


2

ये दो स्कूल वास्तव में बाहर नहीं कर रहे हैं।

यहाँ वह परिदृश्य है जहाँ आपको बस उपयोग करना है

   Task.Run(() => AsyncOperation()).Wait(); 

या ऐसा कुछ

   AsyncContext.Run(AsyncOperation);

मेरे पास एमवीसी कार्रवाई है जो डेटाबेस लेनदेन विशेषता के तहत है। अगर कुछ गलत हुआ तो कार्रवाई में किए गए हर काम को वापस करने के लिए विचार (शायद) था। यह संदर्भ स्विचिंग की अनुमति नहीं देता है, अन्यथा लेन-देन रोलबैक या कमिट स्वयं विफल होने वाला है।

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

एकमात्र विकल्प। इसे सामान्य सिंक कॉल के रूप में चलाएं।

मैं सिर्फ अपने आप से कह रहा हूं।


तो आप अपने जवाब में पहला विकल्प सुझा रहे हैं?
डॉन चेदिले

1

मैं इसे ओपी के लिए प्रत्यक्ष प्रासंगिकता से अधिक पूर्णता के लिए यहां डाल रहा हूं। मैंने लगभग एक दिन डिबगिंग में बिताया HttpClient, यह सोचकर कि मुझे कभी प्रतिक्रिया नहीं मिली।

अंत में पाया गया है कि मैं करने के लिए भूल गया था कॉल स्टैक नीचे आगे कॉल।awaitasync

एक अर्धविराम लापता के रूप में के रूप में अच्छा लगता है।


-1

मैं यहाँ देख रहा हूँ:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx

और यहाँ:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx

और देख रहे हैं:

यह प्रकार और इसके सदस्यों को संकलक द्वारा उपयोग करने के लिए अभिप्रेत है।

awaitसंस्करण को ध्यान में रखते हुए , और काम करने का 'सही' तरीका है, क्या आपको वास्तव में इस प्रश्न का उत्तर चाहिए?

मेरा वोट है: एपीआई दुरुपयोग


मैंने उस पर ध्यान नहीं दिया था, हालाँकि मैंने दूसरी भाषा देखी है जिसके आसपास यह संकेत मिलता है कि GetResult () API का उपयोग एक समर्थित (और अपेक्षित) उपयोग-मामला है।
बेंजामिन फॉक्स

1
इसके अलावा, यदि आप Test5Controller.Get()निम्न के साथ वेटर को खत्म करने के लिए रिफ्लेक्टर करते हैं: var task = AsyncAwait_GetSomeDataAsync(); return task.Result;उसी व्यवहार को देखा जा सकता है।
बेंजामिन फॉक्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.