20

से एसिंक विधियों को कॉल करना मैं एक पुस्तकालय को अद्यतन करने की प्रक्रिया में हूं जिसमें एक एपीआई सतह है जिसे .NET 3.5 में बनाया गया था। नतीजतन, सभी विधियां तुल्यकालिक हैं। मैं एपीआई नहीं बदल सकता (यानी, वापसी मूल्यों को कार्य में परिवर्तित करें) क्योंकि इसकी आवश्यकता होगी कि सभी कॉलर्स बदल जाएंगे। इसलिए मुझे एक सिंक्रोनस तरीके से एसिंक विधियों को सर्वश्रेष्ठ तरीके से कॉल करने के तरीके के साथ छोड़ दिया गया है। यह एएसपी.नेट 4, एएसपी.नेट कोर, और .NET/.NET कोर कंसोल अनुप्रयोगों के संदर्भ में है।गैर-एसिंक कोड

मुझे पर्याप्त स्पष्ट नहीं हो सकता है - स्थिति यह है कि मेरे पास मौजूदा कोड है जो एसिंक जागरूक नहीं है, और मैं System.Net.Http और AWS SDK जैसे नए पुस्तकालयों का उपयोग करना चाहता हूं जो केवल async विधियों का समर्थन करते हैं। तो मुझे अंतराल को पुल करने की जरूरत है, और कोड प्राप्त करने में सक्षम होना जिसे सिंक्रनाइज़ कहा जा सकता है लेकिन फिर कहीं और एसिंक विधियों को कॉल कर सकता है।

मैंने बहुत सी पढ़ाई की है, और कई बार यह पूछा गया है और उत्तर दिया गया है।

Calling async method from non async method

Synchronously waiting for an async operation, and why does Wait() freeze the program here

Calling an async method from a synchronous method

How would I run an async Task<T> method synchronously?

Calling async method synchronously

How to call asynchronous method from synchronous method in C#?

समस्या यह है कि अधिकांश उत्तर अलग हैं! मैंने देखा है कि सबसे आम दृष्टिकोण का उपयोग होता है। रिसेट, लेकिन यह deadlock कर सकते हैं। मैंने निम्नलिखित सभी की कोशिश की है, और वे काम करते हैं, लेकिन मुझे यकीन नहीं है कि डेडलॉक्स से बचने के लिए सबसे अच्छा तरीका कौन सा है, अच्छा प्रदर्शन है, और रनटाइम के साथ अच्छी तरह से खेलता है (कार्य शेड्यूलर, कार्य निर्माण विकल्प इत्यादि के सम्मान में))। क्या कोई निश्चित उत्तर है? सबसे अच्छा तरीका क्या है?

private static T taskSyncRunner<T>(Func<Task<T>> task) 
    { 
     T result; 
     // approach 1 
     result = Task.Run(async() => await task()).ConfigureAwait(false).GetAwaiter().GetResult(); 

     // approach 2 
     result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult(); 

     // approach 3 
     result = task().ConfigureAwait(false).GetAwaiter().GetResult(); 

     // approach 4 
     result = Task.Run(task).Result; 

     // approach 5 
     result = Task.Run(task).GetAwaiter().GetResult(); 


     // approach 6 
     var t = task(); 
     t.RunSynchronously(); 
     result = t.Result; 

     // approach 7 
     var t1 = task(); 
     Task.WaitAll(t1); 
     result = t1.Result; 

     // approach 8? 

     return result; 
    } 
+4

उत्तर यह है कि आप ऐसा नहीं करते हैं। आप नई विधियों को जोड़ते हैं जो असीमित हैं और पुरानी कॉलर के लिए पुराने सिंक्रोनस को रखते हैं। –

+3

यह थोड़ा कठोर लगता है, और वास्तव में नए कोड का उपयोग करने की क्षमता को मारता है। उदाहरण के लिए, एडब्ल्यूएस एसडीके के नए संस्करण में गैर-एसिंक विधियां नहीं हैं। कई अन्य तृतीय पक्ष पुस्तकालयों के लिए वही। तो जब तक आप दुनिया को फिर से लिख नहीं लेते, आप इनमें से किसी का भी उपयोग नहीं कर सकते? –

+0

विकल्प 8: शायद [कार्य पूर्णीकरण स्रोत] (https://msdn.microsoft.com/en-us/library/dd449174 (v = vs.110) .aspx) एक विकल्प हो सकता है? – OrdinaryOrange

उत्तर

32

तो मैं एक तुल्यकालिक तरह से कैसे करने के लिए सबसे अच्छा कॉल async तरीकों के साथ छोड़ दिया हूँ।

सबसे पहले, यह करना ठीक है। मैं यह कह रहा हूं क्योंकि कंक्रीट मामले के संबंध में शैतान के कार्य को कंबल स्टेटमेंट के रूप में इंगित करने के लिए स्टैक ओवरफ़्लो पर यह आम है।

शुद्धता के लिए सभी तरह से एसिंक होने की आवश्यकता नहीं है। इसे सिंक करने के लिए कुछ एसिंक पर अवरुद्ध करना एक प्रदर्शन लागत है जो महत्वपूर्ण हो सकता है या पूरी तरह से अप्रासंगिक हो सकता है। यह ठोस मामले पर निर्भर करता है।

डेडलॉक्स एक ही समय में एक ही एकल थ्रेडेड सिंक्रनाइज़ेशन संदर्भ में प्रवेश करने की कोशिश कर रहे दो धागे से आते हैं। कोई भी तकनीक जो इससे बचाती है, अवरुद्ध होने के कारण डेडलॉक्स से बचाती है।

यहां, .ConfigureAwait(false) पर आपकी सभी कॉल व्यर्थ हैं क्योंकि आप प्रतीक्षा नहीं कर रहे हैं।

RunSynchronously उपयोग करने के लिए अमान्य है क्योंकि सभी कार्यों को इस तरह संसाधित नहीं किया जा सकता है।

.GetAwaiter().GetResult()Result/Wait() से अलग है जिसमें यह await अपवाद प्रसार व्यवहार की नकल करता है। आपको यह तय करने की ज़रूरत है कि आप चाहते हैं या नहीं। (तो अनुसंधान करें कि वह व्यवहार क्या है; इसे दोहराने की कोई आवश्यकता नहीं है।)

इसके अलावा, इन सभी दृष्टिकोणों के समान प्रदर्शन है। वे एक ओएस घटना को एक या दूसरे तरीके से आवंटित करेंगे और उस पर अवरुद्ध करेंगे। यह महंगा हिस्सा है। मुझे नहीं पता कि कौन सा दृष्टिकोण बिल्कुल सस्ता है।

मैं व्यक्तिगत रूप से Task.Run(() => DoSomethingAsync()).Wait(); पैटर्न की तरह है क्योंकि यह गतिरोध स्पष्ट बचा जाता है, आसान है और कुछ अपवाद हैं जो GetResult() छिपाने सकता है छिपा नहीं है। लेकिन आप इसके साथ GetResult() का भी उपयोग कर सकते हैं।

+2

धन्यवाद, यह बहुत समझ में आता है। –

+0

अधिक/क्रॉस-रेफ http://stackoverflow.com/a/22629216/56145 –

10

मैं एक पुस्तकालय एक API सतह कि .NET 3.5 में बनाया गया था है कि अपडेट करने की प्रक्रिया में हूँ। नतीजतन, सभी विधियां तुल्यकालिक हैं। मैं एपीआई नहीं बदल सकता (यानी, वापसी मूल्यों को कार्य में परिवर्तित करें) क्योंकि इसकी आवश्यकता होगी कि सभी कॉलर्स बदल जाएंगे। इसलिए मुझे एक सिंक्रोनस तरीके से एसिंक विधियों को सर्वश्रेष्ठ तरीके से कॉल करने के तरीके के साथ छोड़ दिया गया है।

सिंक-ओवर-एसिंक एंटी-पैटर्न करने के लिए कोई सार्वभौमिक "सर्वश्रेष्ठ" तरीका नहीं है। केवल कुछ प्रकार के हैक जिनमें प्रत्येक की अपनी कमियां होती हैं।

जो मैं अनुशंसा करता हूं वह यह है कि आप पुराने सिंक्रोनस एपीआई रखते हैं और फिर उनके साथ एसिंक्रोनस एपीआई पेश करते हैं। आप इसे "boolean argument hack" as described in my MSDN article on Brownfield Async का उपयोग करके कर सकते हैं।

पहले, अपने उदाहरण में प्रत्येक दृष्टिकोण के साथ समस्याओं का एक संक्षिप्त विवरण:

  1. ConfigureAwait केवल समझ में आता है जब वहाँ एक await; अन्यथा, यह कुछ भी नहीं करता है।
  2. ResultAggregateException में अपवाद लपेटेंगे; अगर आपको ब्लॉक करना है, तो इसके बजाय GetAwaiter().GetResult() का उपयोग करें।
  3. Task.Run अपने कोड को थ्रेड पूल थ्रेड (स्पष्ट रूप से) पर निष्पादित करेगा। यह ठीक है केवल कोड थ्रेड पूल थ्रेड पर चला सकता है।
  4. RunSynchronously एक उन्नत एपीआई है जो गतिशील कार्य-आधारित समांतरता करते समय अत्यंत दुर्लभ परिस्थितियों में उपयोग किया जाता है। आप उस परिदृश्य में बिल्कुल नहीं हैं।
  5. Task.WaitAll एक ही कार्य के साथ Wait() जैसा ही है।
  6. async() => await x() => x कहने का एक कम-कुशल तरीका है।
  7. वर्तमान थ्रेड can cause deadlocks से शुरू किए गए कार्य पर अवरुद्ध करना।

यहाँ टूटने है:

// Problems (1), (3), (6) 
result = Task.Run(async() => await task()).ConfigureAwait(false).GetAwaiter().GetResult(); 

// Problems (1), (3) 
result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult(); 

// Problems (1), (7) 
result = task().ConfigureAwait(false).GetAwaiter().GetResult(); 

// Problems (2), (3) 
result = Task.Run(task).Result; 

// Problems (3) 
result = Task.Run(task).GetAwaiter().GetResult(); 

// Problems (2), (4) 
var t = task(); 
t.RunSynchronously(); 
result = t.Result; 

// Problems (2), (5) 
var t1 = task(); 
Task.WaitAll(t1); 
result = t1.Result; 
इन तरीकों में से किसी के बजाय

, जब से तुम , मौजूदा तुल्यकालिक कोड काम कर रहा है, तो आप यह नये स्वाभाविक रूप से अतुल्यकालिक कोड के साथ प्रयोग करना चाहिए।उदाहरण के लिए, यदि अपने मौजूदा कोड का इस्तेमाल किया WebClient:

public string Get() 
{ 
    using (var client = new WebClient()) 
    return client.DownloadString(...); 
} 

और आप तो मैं इस तरह यह करना होगा एक async एपीआई जोड़ना चाहते हैं,:

private async Task<string> GetCoreAsync(bool sync) 
{ 
    using (var client = new WebClient()) 
    { 
    return sync ? 
     client.DownloadString(...) : 
     await client.DownloadStringTaskAsync(...); 
    } 
} 

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult(); 

public Task<string> GetAsync() => GetCoreAsync(sync: false); 

या, यदि आप चाहिए उपयोग HttpClient किसी कारण से:

private string GetCoreSync() 
{ 
    using (var client = new WebClient()) 
    return client.DownloadString(...); 
} 

private static HttpClient HttpClient { get; } = ...; 

private async Task<string> GetCoreAsync(bool sync) 
{ 
    return sync ? 
     GetCoreSync() : 
     await HttpClient.GetString(...); 
} 

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult(); 

public Task<string> GetAsync() => GetCoreAsync(sync: false); 

इस दृष्टिकोण के साथ, अपने तर्क Core तरीकों में जाना होगा, हो सकता है समकालिक रूप से या असीमित रूप से चलाएं (जैसा कि sync पैरामीटर द्वारा निर्धारित किया गया है)। यदि synctrue है, तो मूल विधियों पहले से ही पूरा कार्य वापस कर देना चाहिए। लागू करने के लिए, सिंक्रोनस एपीआई का उपयोग सिंक्रनाइज़ करने के लिए करें, और असीमित रूप से चलाने के लिए एसिंक्रोनस एपीआई का उपयोग करें।

आखिरकार, मैं सिंक्रोनस एपीआई को बहिष्कृत करने की अनुशंसा करता हूं।

+0

क्या आप छः आइटम को समझा सकते हैं? –

+0

@EmersonSoares: जैसा कि मैंने अपने [async परिचय] में व्याख्या की है (http://blog.stephencleary.com/2012/02/async-and-await.html), आप किसी विधि के परिणाम का इंतजार कर सकते हैं क्योंकि यह लौटाता है 'कार्य', क्योंकि यह 'async' नहीं है। इसका मतलब है कि [छोटी विधियों के लिए, आप कीवर्ड छोड़ सकते हैं] (http://blog.stephencleary.com/2016/12/eliding-async-await.html)। –

+0

मुझे हाल ही में एएसपीनेट एमवीसी 5 + ईएफ 6 में एक ही समस्या का सामना करना पड़ा। मैंने टास्कफ़ैक्टरी का उपयोग किया क्योंकि यह जवाब https://stackoverflow.com/a/25097498/1683040 सुझाता है, और यह मेरे लिए चाल है :), लेकिन अन्य परिदृश्यों के लिए सुनिश्चित नहीं है। – LeonardoX