c# - HttpClient.GetAsync(...) प्रतीक्षा/async का उपयोग करते समय कभी वापस नहीं आती है




.net asynchronous (4)

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

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

यह एएसपी . NET SynchronizationContext कॉन्टेक्स्ट द्वारा प्रबंधित किया जाता है

डिफ़ॉल्ट रूप से, जब आप किसी Task await करते हैं, तो विधि एक कैप्चर SynchronizationContext TaskScheduler पर शुरू होती है (या कोई SynchronizationContext TaskScheduler , तो एक कैप्चर TaskScheduler )। आम तौर पर, यह वही है जो आप चाहते हैं: एक एसिंक्रोनस कंट्रोलर एक्शन कुछ 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.GetTask पूरा होने तक वर्तमान धागे को ब्लॉक करें।
  • HTTP प्रतिक्रिया आती है, और HttpClient.GetAsync द्वारा लौटा गया Task पूरा हो गया है।
  • AsyncAwait_GetSomeDataAsync ASP.NET अनुरोध संदर्भ में फिर से शुरू करने का प्रयास करता है। हालांकि, उस संदर्भ में पहले से ही एक धागा है: थ्रेड Test5Controller.Get में अवरुद्ध।
  • गतिरोध।

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

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

और यहां सर्वोत्तम प्रथाएं हैं:

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

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

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

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

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

संपादित करें: परीक्षण मामले 5 में कार्य WaitingForActivation करने के लिए WaitingForActivation स्थिति में फंस गया प्रतीत होता है।

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

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

यहां कुछ कोड है जो समस्या को पुन: उत्पन्न करते हैं - इसे निम्नलिखित GET अंतराल का पर्दाफाश करने के लिए विजुअल स्टूडियो 11 में एक नई "एमवीसी 4 वेबएपीआई परियोजना" में छोड़ दें:

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

यहां दिए गए प्रत्येक अंतराल में एक ही डेटा (stackoverflow.com से प्रतिक्रिया शीर्षलेख) को वापस /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();
    }
}

चूंकि आप .Result उपयोग कर रहे हैं। .Result या .Wait या इसका await आपके कोड में डेडलॉक का कारण बन जाएगा।

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

इस तरह:

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

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


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

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

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

या कुछ पसंद है

   AsyncContext.Run(AsyncOperation);

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

मुझे जिस पुस्तकालय की आवश्यकता है वह async है क्योंकि यह async चलाने की उम्मीद है।

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

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


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

here से त्वरित फिक्स। लिखने के बजाय:

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

प्रयत्न:

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

या यदि आपको परिणाम चाहिए:

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

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

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

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

स्रोत से:

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

int सिंक () {वापसी कार्य। रुन (() => लाइब्रेरी.फूएसिंक ()) परिणाम। }

FooAsync को अब थ्रेडपूल पर बुलाया जाएगा, जहां सिंक्रनाइज़ेशन कॉन्टेक्स्ट नहीं होगा, और FooAsync के अंदर उपयोग की जाने वाली निरंतरता को उस थ्रेड पर वापस नहीं मजबूर किया जाएगा जो सिंक () का आविष्कार कर रहा है।





dotnet-httpclient