एक async / प्रतीक्षा उदाहरण जो गतिरोध का कारण बनता है


94

मुझे एस # एस async/ awaitकीवर्ड (मैं सी # 5.0 पर नया हूं) का उपयोग करके अतुल्यकालिक प्रोग्रामिंग के लिए कुछ सर्वोत्तम प्रथाओं में आया हूं ।

दी गई एक सलाह निम्नलिखित थी:

स्थिरता: अपने सिंक्रनाइज़ेशन संदर्भों को जानें

... कुछ सिंक्रनाइज़ेशन संदर्भ नॉन-रीएंन्ट्रेंट और सिंगल-थ्रेडेड हैं। इसका अर्थ है कि किसी निश्चित समय में केवल एक इकाई कार्य को निष्पादित किया जा सकता है। इसका एक उदाहरण Windows UI थ्रेड या ASP.NET अनुरोध संदर्भ है। इन एकल-थ्रेडेड सिंक्रनाइज़ेशन संदर्भों में, अपने आप को गतिरोध करना आसान है। यदि आप किसी कार्य को एकल-थ्रेडेड संदर्भ से अलग करते हैं, तो संदर्भ में उस कार्य की प्रतीक्षा करें, आपका प्रतीक्षा कोड पृष्ठभूमि कार्य को अवरुद्ध कर सकता है।

public ActionResult ActionAsync()
{
    // DEADLOCK: this blocks on the async task
    var data = GetDataAsync().Result;

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

अगर मैं इसे खुद से अलग करने की कोशिश करता हूं, तो मुख्य धागा एक नए में पैदा होता है MyWebService.GetDataAsync();, लेकिन चूंकि मुख्य धागा वहां इंतजार करता है, इसलिए यह परिणाम पर इंतजार करता है GetDataAsync().Result। इस बीच, यह कहें कि डेटा तैयार है। मुख्य सूत्र क्यों जारी नहीं रखता है यह निरंतरता तर्क है और इससे एक स्ट्रिंग परिणाम देता है GetDataAsync()?

क्या कोई मुझे समझा सकता है कि उपरोक्त उदाहरण में गतिरोध क्यों है? मुझे पूरी तरह पता है कि समस्या क्या है ...


क्या आपको वाकई यकीन है कि GetDataAsync का सामान खत्म हो गया है? या यह सिर्फ ताला और गतिरोध के कारण अटक जाता है?
एंड्री

यह वह उदाहरण है जो प्रदान किया गया था। मेरी समझ में यह खत्म हो जाना चाहिए और सामान तैयार होना चाहिए ...
Dror Weiss

4
आप भी कार्य की प्रतीक्षा में क्यों हैं? आपको इसके बजाय इंतजार करना चाहिए क्योंकि आप मूल रूप से async मॉडल के सभी लाभों को खो देते हैं।
टोनी पेट्रीना

@ ToniPetrina के बिंदु में जोड़ने के लिए, यहां तक ​​कि w / o गतिरोध की समस्या है, var data = GetDataAsync().Result;कोड की एक पंक्ति है जिसे कभी भी इस संदर्भ में नहीं किया जाना चाहिए कि आपको ब्लॉक नहीं करना चाहिए (UI या ASP.NET अनुरोध)। यहां तक ​​कि अगर यह गतिरोध नहीं करता है, तो यह थ्रेड को अनिश्चित समय तक रोक रहा है। तो मूल रूप से इसका एक भयानक उदाहरण है। [आपको उस कोड को निष्पादित करने से पहले यूआई थ्रेड से बाहर निकलने की आवश्यकता है, या awaitवहां भी उपयोग करें , जैसा कि टोनी का सुझाव है।]
टूलमेकरसेव

जवाबों:


81

इस उदाहरण पर एक नज़र डालें , स्टीफन का आपके लिए स्पष्ट जवाब है:

तो यह वही होता है, जो शीर्ष-स्तरीय विधि ( Button1_ClickUI / MyController.GetASP.NET के लिए) से शुरू होता है:

  1. शीर्ष-स्तरीय विधि कॉल GetJsonAsync(UI / ASP.NET संदर्भ के भीतर)।

  2. GetJsonAsyncकॉल करके HttpClient.GetStringAsync(अभी भी संदर्भ के भीतर) REST अनुरोध प्रारंभ करता है ।

  3. GetStringAsyncएक अपूर्ण रिटर्न देता है Task, जो दर्शाता है कि REST अनुरोध पूरा नहीं हुआ है।

  4. GetJsonAsyncप्रतीक्षा कर रहा है Taskद्वारा लौटाए गए GetStringAsync। संदर्भ कैप्चर किया गया है और GetJsonAsyncबाद में विधि को जारी रखने के लिए उपयोग किया जाएगा । GetJsonAsyncएक अपूर्ण रिटर्न देता है Task, यह दर्शाता है कि GetJsonAsyncविधि पूरी नहीं है।

  5. शीर्ष स्तर की विधि Taskद्वारा लौटाए गए पर सिंक्रोनाइज़ करता है GetJsonAsync। यह संदर्भ थ्रेड को ब्लॉक करता है।

  6. ... आखिरकार, REST अनुरोध पूरा हो जाएगा। यह पूरा करता है Taskकि द्वारा वापस आ गया था GetStringAsync

  7. इसके लिए निरंतरता GetJsonAsyncअब चलाने के लिए तैयार है, और यह संदर्भ के लिए उपलब्ध होने की प्रतीक्षा करता है ताकि यह संदर्भ में निष्पादित हो सके।

  8. गतिरोध । शीर्ष-स्तरीय विधि संदर्भ थ्रेड को अवरुद्ध कर रही है GetJsonAsync, पूर्ण होने GetJsonAsyncकी प्रतीक्षा कर रही है और संदर्भ के मुक्त होने की प्रतीक्षा कर रही है ताकि यह पूरा हो सके। UI उदाहरण के लिए, "संदर्भ" UI संदर्भ है; ASP.NET उदाहरण के लिए, "संदर्भ" ASP.NET अनुरोध संदर्भ है। इस प्रकार का गतिरोध या तो "संदर्भ" के कारण हो सकता है।

एक और लिंक जो आपको पढ़ना चाहिए: Await, और UI, और गतिरोध! अरे बाप रे!


20
  • Fact 1: GetDataAsync().Result;जब कार्य GetDataAsync()पूरा हो जाएगा तब चलेगा , इस बीच यह UI थ्रेड को ब्लॉक करता है
  • तथ्य 2: प्रतीक्षा की निरंतरता ( return result.ToString()) निष्पादन के लिए UI थ्रेड पर पंक्तिबद्ध है
  • फैक्ट 3: इसके द्वारा GetDataAsync()जारी कार्य तब पूरा होगा जब इसकी कतार जारी रहेगी
  • तथ्य 4: कतारबद्ध निरंतरता कभी नहीं चलती, क्योंकि UI थ्रेड अवरुद्ध है (तथ्य 1)

गतिरोध!

तथ्य 1 या तथ्य 2 से बचने के लिए विकल्प प्रदान करके गतिरोध को तोड़ा जा सकता है।

  • 1,4 से बचें। यूआई थ्रेड को अवरुद्ध करने के बजाय, उपयोग करें var data = await GetDataAsync(), जो यूआई थ्रेड को चालू रखने की अनुमति देता है
  • 2,3 से बचें। किसी भिन्न थ्रेड के लिए प्रतीक्षा की निरंतरता को पंक्तिबद्ध करें var data = Task.Run(GetDataAsync).Resultजो अवरुद्ध नहीं है, उदाहरण के लिए उपयोग करें , जो थ्रेडल थ्रेड के सिंक संदर्भ के लिए निरंतरता को पोस्ट करेगा। यह कार्य GetDataAsync()को पूरा करने की अनुमति देता है ।

यह स्टीफन टौब के एक लेख में वास्तव में अच्छी तरह से समझाया गया है , लगभग आधे रास्ते में जहां वह उदाहरण का उपयोग करता है DelayAsync()


मेरे बारे में var data = Task.Run(GetDataAsync).Result, यह नया है। मैंने हमेशा सोचा था कि बाहरी हिट के .Resultतुरंत बाद आसानी से उपलब्ध GetDataAsyncहो dataजाएगा , इसलिए हमेशा रहेगा default। दिलचस्प।
नवफाल

18

मैं एक ASP.NET MVC प्रोजेक्ट में फिर से इस मुद्दे के साथ काम कर रहा था। जब आप asyncविधियों को कॉल करना चाहते हैं PartialView, तो आपको बनाने की अनुमति नहीं है PartialView async। यदि आप ऐसा करेंगे तो आपको एक अपवाद मिलेगा।

आप निम्न सरल वर्कअराउंड का उपयोग उस परिदृश्य में कर सकते हैं जहाँ आप किसी asyncविधि को सिंक विधि से कॉल करना चाहते हैं :

  1. कॉल से पहले, साफ़ करें SynchronizationContext
  2. कॉल करें, यहां कोई और गतिरोध नहीं होगा, इसके खत्म होने की प्रतीक्षा करें
  3. पुनर्स्थापित करें SynchronizationContext

उदाहरण:

public ActionResult DisplayUserInfo(string userName)
{
    // trick to prevent deadlocks of calling async method 
    // and waiting for on a sync UI thread.
    var syncContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    //  this is the async call, wait for the result (!)
    var model = _asyncService.GetUserInfo(Username).Result;

    // restore the context
    SynchronizationContext.SetSynchronizationContext(syncContext);

    return PartialView("_UserInfo", model);
}

3

एक और मुख्य बिंदु यह है कि आपको टास्क पर ब्लॉक नहीं करना चाहिए, और डेडलॉक को रोकने के लिए सभी तरह से एसिंक्स का उपयोग करना चाहिए। तब यह सभी अतुल्यकालिक तुल्यकालिक अवरुद्ध नहीं होगा।

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

6
क्या होगा अगर मैं चाहता हूं कि मुख्य (UI) धागा कार्य समाप्त होने तक अवरुद्ध हो जाए? या उदाहरण के लिए एक कंसोल ऐप में? मान लीजिए कि मैं HttpClient का उपयोग करना चाहता हूं, जो केवल async का समर्थन करता है ... मैं इसे गतिरोध के जोखिम के बिना सिंक्रोनस का उपयोग कैसे करूं ? यह संभव होना चाहिए। यदि WebClient का उपयोग इस तरह से किया जा सकता है (सिंक विधियों के होने के कारण) और पूरी तरह से काम करता है, तो इसे HttpClub के साथ भी क्यों नहीं किया जा सकता है?
Dexter

ऊपर फिलिप नगन का उत्तर देखें (मुझे पता है कि यह इस टिप्पणी के बाद पोस्ट किया गया था): एक अलग थ्रेड के लिए प्रतीक्षा की निरंतरता को रोकें जो अवरुद्ध नहीं है, उदाहरण के लिए var data = Task.Run (GetDataAsync) का उपयोग करें ।Result
Jeroen

@Dexter - re " क्या होगा अगर मैं चाहता हूं कि मुख्य (UI) धागा कार्य समाप्त होने तक अवरुद्ध हो? " - क्या आप वास्तव में UI थ्रेड को अवरुद्ध करना चाहते हैं, जिसका अर्थ है कि उपयोगकर्ता कुछ भी नहीं कर सकता है, रद्द भी नहीं कर सकता - या है यह है कि आप जिस विधि में हैं उसे जारी नहीं रखना चाहते हैं? "वेट" या "टास्क.कंटीन्यू वाइट" बाद वाले मामले को संभालता है।
टूलमेकरसेव

@ToolmakerSteve बेशक मैं इस विधि को जारी नहीं रखना चाहता। लेकिन मैं बस इंतजार का उपयोग नहीं कर सकता क्योंकि मैं सभी तरह से async का उपयोग नहीं कर सकता - HttpClient मुख्य रूप से लागू किया गया है , जो निश्चित रूप से async नहीं हो सकता है। और फिर मैंने एक कंसोल ऐप में यह सब करने का उल्लेख किया है - इस मामले में मैं बिल्कुल पूर्व चाहता हूं - मैं नहीं चाहता कि मेरा ऐप मल्टी-थ्रेडेड भी होसब कुछ अवरुद्ध ।
डेक्सटर

-1

मेरे पास एक काम Joinयह है कि परिणाम के लिए पूछने से पहले कार्य पर एक विस्तार विधि का उपयोग करें ।

कोड इस तरह दिखता है:

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

जहां शामिल होने की विधि है:

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

मैं इस समाधान की कमियां (यदि कोई हो) देखने के लिए डोमेन में पर्याप्त नहीं हूं

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