सभी कार्यों को डिफ़ॉल्ट रूप से async क्यों नहीं होना चाहिए?


107

Async-इंतजार .net 4.5 के पैटर्न प्रतिमान बदलते है। यह सच होने के लिए लगभग बहुत अच्छा है।

मैं कुछ IO- भारी कोड को async-wait में पोर्ट कर रहा हूं क्योंकि ब्लॉक करना अतीत की बात है।

बहुत से लोग एक ज़ोंबी infestation के लिए async-wait की तुलना कर रहे हैं और मैंने पाया कि यह सटीक है। Async कोड अन्य async कोड को पसंद करता है (आपको async फ़ंक्शन पर प्रतीक्षा करने के लिए एक async फ़ंक्शन की आवश्यकता होती है)। तो अधिक से अधिक फ़ंक्शन असिंस्क हो जाते हैं और यह आपके कोडबेस में बढ़ता रहता है।

Async में फ़ंक्शन बदलना कुछ दोहराव और अकल्पनीय काम है। asyncघोषणा में एक कीवर्ड फेंको , वापसी मूल्य को लपेटो Task<>और आप बहुत अधिक काम कर रहे हैं। यह पूरी तरह से आसान नहीं है, बल्कि बहुत ही अस्थिर है, और बहुत जल्द एक टेक्स्ट-रिप्लेसमेंट स्क्रिप्ट मेरे लिए "पोर्टिंग" के अधिकांश भाग को स्वचालित कर देगी।

और अब सवाल .. अगर मेरा सभी कोड धीरे-धीरे async बदल रहा है, तो बस डिफ़ॉल्ट रूप से सभी async क्यों नहीं बनाते?

मुझे लगता है कि स्पष्ट कारण प्रदर्शन है। Async-wait का अपना ओवरहेड और कोड है जिसे async होने की आवश्यकता नहीं है, अधिमानतः नहीं होना चाहिए। लेकिन अगर प्रदर्शन एकमात्र समस्या है, तो निश्चित रूप से कुछ चतुर अनुकूलन ओवरहेड को स्वचालित रूप से हटा सकते हैं जब इसकी आवश्यकता नहीं होती है। मैंने "फास्ट पथ" ऑप्टिमाइज़ेशन के बारे में पढ़ा है , और यह मुझे लगता है कि इसे अकेले इसका सबसे अधिक ध्यान रखना चाहिए।

हो सकता है कि यह कचरा संग्रहकर्ताओं द्वारा लाए गए प्रतिमान की तुलना में हो। शुरुआती जीसी दिनों में, अपनी खुद की मेमोरी को मुक्त करना निश्चित रूप से अधिक कुशल था। लेकिन जनता ने अभी भी सुरक्षित, सरल कोड के पक्ष में स्वत: संग्रह को चुना जो कि कम कुशल हो सकता है (और यह भी कि अब सच नहीं है)। शायद यहाँ भी यही होना चाहिए? सभी कार्य क्यों नहीं होने चाहिए?


8
मानचित्र को चिह्नित करने का श्रेय C # टीम को दें। जैसे यह सैकड़ों साल पहले किया गया था, "ड्रेगन यहाँ झूठ"। आप एक जहाज से लैस कर सकते हैं और वहाँ जा सकते हैं, बहुत संभावना है कि आप इसे सूर्य चमक और अपनी पीठ में हवा के साथ जीवित रखेंगे। और कभी नहीं, वे कभी नहीं लौटे। बहुत कुछ async / प्रतीक्षा की तरह, SO उपयोगकर्ताओं के सवालों से भरा है जो यह नहीं समझ पाए कि वे मानचित्र से कैसे हट गए। हालांकि उन्हें बहुत अच्छी चेतावनी मिली। अब यह उनकी समस्या है, C # टीम की समस्या नहीं। उन्होंने ड्रैगनों को चिह्नित किया।
हंस पसंत

1
@ यदि आप सिंक और एसिंक्स फ़ंक्शंस के बीच के अंतर को दूर करते हैं, तो भी कॉलिंग फ़ंक्शंस जो सिंक्रोनाइज़ किए जाते हैं, तब भी सिंक्रोनस रहेगा (जैसे आपका
लिक्लाइन

2
"टास्क द्वारा रिटर्न वैल्यू को लपेटें <>" जब तक आपकी विधि async(कुछ अनुबंध को पूरा करने के लिए) नहीं होती है, यह संभवतः एक बुरा विचार है। आपको async (पद्धति कॉल की लागत में वृद्धि await, कॉलिंग कोड में उपयोग करने की आवश्यकता ) के नुकसान हो रहे हैं , लेकिन कोई भी लाभ नहीं है।
svick

1
यह वास्तव में एक दिलचस्प सवाल है, लेकिन शायद एसओ के लिए एक महान फिट नहीं है। हम इसे प्रोग्रामर को माइग्रेट करने पर विचार कर सकते हैं।
एरिक लिपिपर्ट

1
शायद मुझे कुछ याद आ रहा है, लेकिन मेरे पास बिल्कुल वही सवाल है। यदि async / प्रतीक्षा हमेशा जोड़े में आती है और कोड को अभी भी निष्पादित करने के लिए इंतजार करना पड़ता है, तो क्यों न केवल मौजूदा .NET फ्रेमवर्क को अपडेट किया जाए, और उन तरीकों को बनाएं जो अतिरिक्त कीवर्ड बनाने के बिना डिफ़ॉल्ट रूप से async - async होने की आवश्यकता है? भाषा पहले से ही वही है जो इसे बचने के लिए डिज़ाइन की गई थी - कीवर्ड स्पेगेटी। मुझे लगता है कि उन्हें "var" सुझाए जाने के बाद ऐसा करना बंद कर देना चाहिए था। अब हमारे पास "डायनेमिक", एसाइन / वेट ... आदि हैं ... न ही यू लोग सिर्फ .NET-ify जावास्क्रिप्ट क्यों? ;))
मोनस्टरो

जवाबों:


127

सबसे पहले, अपनी तरह के शब्दों के लिए धन्यवाद। यह वास्तव में एक कमाल की विशेषता है और मुझे इसका एक छोटा सा हिस्सा होने की खुशी है।

यदि मेरा सभी कोड धीरे-धीरे async में बदल रहा है, तो बस इसे डिफ़ॉल्ट रूप से सभी async क्यों नहीं बनाते?

खैर, आप अतिशयोक्ति कर रहे हैं; आपका सभी कोड async नहीं बदल रहा है। जब आप एक साथ दो "सादे" पूर्णांक जोड़ते हैं, तो आप परिणाम की प्रतीक्षा नहीं कर रहे हैं। जब आप तीसरे भविष्य के पूर्णांक प्राप्त करने के लिए भविष्य के दो पूर्णांकों को एक साथ जोड़ते हैं - क्योंकि यही है, यह एक पूर्णांक है जिसे आप भविष्य में उपयोग करने जा रहे हैं - निश्चित रूप से आपको परिणाम की प्रतीक्षा होगी।Task<int>

सबकुछ नहीं बनाने का प्राथमिक कारण यह है कि async / प्रतीक्षा का उद्देश्य कई उच्च विलंबता कार्यों के साथ दुनिया में कोड लिखना आसान बनाना है । आपके संचालन का अधिकांश हिस्सा उच्च विलंबता नहीं है, इसलिए यह उस विलंब को कम करने वाले प्रदर्शन को हिट करने के लिए कोई मतलब नहीं रखता है। बल्कि, आपके कुछ महत्वपूर्ण ऑपरेशन उच्च विलंबता वाले होते हैं, और उन ऑपरेशनों के कारण कोड में एसिंक्स की ज़ोंबी इन्फ़ेक्शन होता है।

यदि प्रदर्शन एकमात्र समस्या है, तो निश्चित रूप से कुछ चतुर अनुकूलन ओवरहेड को स्वचालित रूप से हटा सकते हैं जब इसकी आवश्यकता नहीं होती है।

सिद्धांत रूप में, सिद्धांत और व्यवहार समान हैं। व्यवहार में, वे कभी नहीं होते हैं।

इस प्रकार के परिवर्तन के खिलाफ मुझे तीन अंक देने चाहिए, उसके बाद एक अनुकूलन पास।

पहला बिंदु फिर से है: C # / VB / F # में async अनिवार्य रूप से निरंतरता का सीमित रूप है । कार्यात्मक भाषा समुदाय में भारी मात्रा में शोध यह पता लगाने के तरीकों में बदल गया है कि कोड को कैसे अनुकूलित किया जाए जो निरंतर गुजरने वाली शैली का भारी उपयोग करता है। कंपाइलर टीम को संभवतः एक ऐसी दुनिया में समान समस्याओं को हल करना होगा जहां "async" डिफ़ॉल्ट थी और गैर-async विधियों को पहचानना और de-async-ified होना था। C # टीम वास्तव में खुली अनुसंधान समस्याओं को लेने में दिलचस्पी नहीं रखती है, इसलिए यह वहीं के बड़े बिंदु हैं।

इसके खिलाफ एक दूसरा बिंदु यह है कि C # में "रेफ़रेंशियल ट्रांसपेरेंसी" का स्तर नहीं है, जो इन प्रकार के अनुकूलन को और अधिक ट्रैफ़िक बनाता है। "रेफ़रेंशियल ट्रांसपेरेंसी" से मेरा तात्पर्य उस संपत्ति से है जिसका मूल्यांकन करने पर किसी अभिव्यक्ति का मूल्य निर्भर नहीं करता है । जैसी अभिव्यक्तियाँ 2 + 2प्रासंगिक रूप से पारदर्शी होती हैं; यदि आप चाहते हैं तो आप संकलन समय पर मूल्यांकन कर सकते हैं, या इसे रनटाइम तक स्थगित कर सकते हैं और समान उत्तर प्राप्त कर सकते हैं। लेकिन एक्स और वाई समय के साथ बदल सकते हैंx+y क्योंकि एक अभिव्यक्ति की तरह समय के आसपास स्थानांतरित नहीं किया जा सकता है

जब कोई साइड इफ़ेक्ट होगा, तो इस बारे में Async बहुत कठिन हो जाता है। Async से पहले, अगर आपने कहा:

M();
N();

और M()था void M() { Q(); R(); }, और N()था void N() { S(); T(); }, और Rऔर Sउत्पादन साइड इफेक्ट है, तो आप जानते हैं कि आर के पक्ष प्रभाव एस के पक्ष प्रभाव से पहले होता है। लेकिन अगर आपके पास है async void M() { await Q(); R(); }तो अचानक वह खिड़की से बाहर चला जाता है। आपको इस बात की कोई गारंटी नहीं R()है कि इससे पहले या बाद में क्या होने वाला है S()(जब तक कि निश्चित रूप M()से प्रतीक्षित नहीं है; लेकिन निश्चित रूप से इसकी Taskआवश्यकता का इंतजार नहीं किया जाना चाहिए N())।

अब कल्पना करें कि आपके प्रोग्राम में कोड के हर टुकड़े पर लागू होने वाले आदेशों के प्रभाव के बारे में पता नहीं क्या-क्या होता है, सिवाय इसके कि ऑप्टिमाइज़र डी-एसिंक्स-इफी को प्रबंधित करता है। मूल रूप से आपके पास कोई सुराग नहीं है कि किन भावों का मूल्यांकन किस क्रम में किया जाएगा, जिसका अर्थ है कि सभी अभिव्यक्तियों को प्रासंगिक रूप से पारदर्शी होना चाहिए, जो कि C # जैसी भाषा में कठिन है।

इसके खिलाफ एक तीसरा बिंदु यह है कि आपको फिर पूछना होगा "async इतना विशेष क्यों है?" यदि आप यह तर्क देने जा रहे हैं कि हर ऑपरेशन वास्तव में होना चाहिए Task<T>तो आपको प्रश्न का उत्तर देने में सक्षम होने की आवश्यकता है "क्यों नहीं Lazy<T>?" या "क्यों नहीं Nullable<T>?" या "क्यों नहीं IEnumerable<T>?" क्योंकि हम बस इतनी आसानी से कर सकते थे। ऐसा क्यों नहीं होना चाहिए कि प्रत्येक ऑपरेशन को अशक्त करने के लिए उठाया जाता है ? या प्रत्येक ऑपरेशन को आलसी रूप से गणना की जाती है और परिणाम बाद के लिए कैश किया जाता है , या हर ऑपरेशन का परिणाम केवल एक मूल्य के बजाय मूल्यों का एक क्रम होता है । फिर आपको उन स्थितियों को अनुकूलित करने का प्रयास करना होगा जहां आप जानते हैं कि "ओह, यह कभी भी अशक्त नहीं होना चाहिए, इसलिए मैं बेहतर कोड उत्पन्न कर सकता हूं", और इसी तरह।

इशारा किया जा रहा है: यह मेरे लिए स्पष्ट नहीं है कि Task<T>वास्तव में यह बहुत काम वारंट है।

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


बिना प्रतीक्षा के कहे जाने वाले async फ़ंक्शन होने से मुझे (सामान्य मामले में) कोई मतलब नहीं है। यदि हम इस सुविधा को हटाते हैं, तो कंपाइलर स्वयं तय कर सकता है कि कोई फ़ंक्शन async है या नहीं (क्या इसे कॉल का इंतजार है?)। तब हम दोनों मामलों (async और सिंक) के लिए समान वाक्यविन्यास कर सकते हैं, और केवल विभेदक के रूप में कॉल में प्रतीक्षा का उपयोग कर सकते हैं। ज़ोंबी
उल्लंघन

मैंने आपके अनुरोध के अनुसार प्रोग्रामर में चर्चा जारी रखी है: programmers.stackexchange.com/questions/209872/…
talkol

@ EricLippert - हमेशा की तरह बहुत अच्छा जवाब :) मैं उत्सुक था अगर आप "उच्च विलंबता" को स्पष्ट कर सकते थे? क्या यहां मिलीसेकंड में एक सामान्य सीमा है? मैं सिर्फ यह पता लगाने की कोशिश कर रहा हूं कि निचली सीमा रेखा एस्क् यू का उपयोग करने के लिए कहां है क्योंकि मैं इसका दुरुपयोग नहीं करना चाहता।
ट्रैविस जे

7
@TravisJ: मार्गदर्शन है: 30 से अधिक ms के लिए UI थ्रेड को ब्लॉक न करें। इससे अधिक और आप उपयोगकर्ता द्वारा ध्यान देने योग्य ठहराव के जोखिम को चलाते हैं।
एरिक लिपर्ट

1
मुझे जो चुनौतियां हैं, वह यह है कि क्या कुछ किया गया है या नहीं, अतुल्यकालिक रूप से एक कार्यान्वयन विवरण है जो बदल सकता है। लेकिन उस कार्यान्वयन में परिवर्तन उस कोड के माध्यम से एक लहर प्रभाव को मजबूर करता है जो इसे कॉल करता है, वह कोड जो कॉल करता है, और इसी तरह। हम कोड बदलने के कारण इसे समाप्त करते हैं, यह उस पर निर्भर करता है, जो कि ऐसी चीज है जिससे हम सामान्य रूप से बचने के लिए महान लंबाई में जाते हैं। या हम उपयोग करते हैं async/awaitक्योंकि अमूर्त की परतों के नीचे छिपी कुछ अतुल्यकालिक हो सकती है।
स्कॉट हैनन

23

सभी कार्य क्यों नहीं होने चाहिए?

जैसा कि आपने बताया, प्रदर्शन एक कारण है। ध्यान दें कि "फास्ट पथ" विकल्प जो आप से जुड़ा हुआ है, एक पूर्ण कार्य के मामले में प्रदर्शन में सुधार करता है, लेकिन इसके लिए एकल विधि कॉल की तुलना में अभी भी बहुत अधिक निर्देशों और ओवरहेड की आवश्यकता होती है। जैसे, यहां तक ​​कि "तेज़ पथ" के साथ, आप प्रत्येक एसिंक्स विधि कॉल के साथ बहुत अधिक जटिलता और ओवरहेड जोड़ रहे हैं।

पीछे की संगतता, साथ ही अन्य भाषाओं (इंटरॉप परिदृश्यों सहित) के साथ संगतता भी समस्याग्रस्त हो जाएगी।

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

इसके अलावा, कई रूटीन हैं जो उनके स्वभाव से, अतुल्यकालिक नहीं हैं। वे तुल्यकालिक संचालन के रूप में अधिक समझ में आता है। जबरदस्ती Math.Sqrtहोने के लिए Task<double> Math.SqrtAsyncहास्यास्पद हो सकता है, उदाहरण के लिए, के रूप में वहाँ उस के लिए सब पर कोई कारण नहीं अतुल्यकालिक होने की है। asyncअपने एप्लिकेशन के माध्यम से पुश करने के बजाय , आप हर जगहawait प्रचार करना समाप्त कर देंगे ।

यह वर्तमान प्रतिमान को भी पूरी तरह से तोड़ देगा, साथ ही गुणों के साथ मुद्दों का कारण भी होगा (जो प्रभावी रूप से सिर्फ विधि जोड़े हैं .. क्या वे async भी जाएंगे?), और फ्रेमवर्क और भाषा के डिजाइन में अन्य नतीजे हैं।

यदि आप बहुत सारे IO बाउंड कार्य कर रहे हैं, तो आप पाएंगे कि asyncव्यापक रूप से उपयोग करना एक बढ़िया अतिरिक्त है, आपकी कई दिनचर्याएँ होंगी async। हालांकि, जब आप सीपीयू बाउंड काम करना शुरू करते हैं, तो सामान्य तौर पर, चीजें बनाना asyncवास्तव में अच्छा नहीं होता है - यह इस तथ्य को छिपा रहा है कि आप एक एपीआई के तहत सीपीयू चक्र का उपयोग कर रहे हैं जो कि अतुल्यकालिक प्रतीत होता है, लेकिन वास्तव में वास्तव में अतुल्यकालिक नहीं है।


वास्तव में मैं क्या लिखने जा रहा था (प्रदर्शन), पीछे की संगतता एक और बात हो सकती है, पुरानी भाषाओं के साथ प्रयोग करने के लिए dll है जो async का समर्थन नहीं करता / प्रतीक्षा भी करता है
Sayse

यदि हम केवल सिंक और async कार्यों के बीच के अंतर को दूर करते हैं, तो sqrt async हास्यास्पद नहीं है
talkol

@talkol मुझे लगता है कि मैं इसे चारों ओर मोड़ दूँगा - हर फ़ंक्शन कॉल को अतुल्यकालिकता की जटिलता पर क्यों लेना चाहिए ?
रीड कोपसे

2
@talkol मेरा तर्क है कि यह सच नहीं है - अतुल्यकालिक ही कीड़े को जोड़ सकता है जो अवरुद्ध से भी बदतर हैं ...
रीड कोप्स

1
@talkol की await FooAsync()तुलना में सरल कैसे है Foo()? और कुछ समय में छोटे डोमिनोज़ प्रभाव के बजाय, आपके पास हर समय विशाल डोमिनोज़ प्रभाव होता है और आप इसे सुधार कहते हैं?
23

4

एक तरफ प्रदर्शन - async में एक उत्पादकता लागत हो सकती है। क्लाइंट (WinForms, WPF, Windows Phone) पर यह उत्पादकता के लिए एक वरदान है। लेकिन सर्वर पर, या अन्य गैर-यूआई परिदृश्यों में, आप उत्पादकता का भुगतान करते हैं । आप निश्चित रूप से वहाँ डिफ़ॉल्ट रूप से async नहीं जाना चाहते हैं। स्केलेबिलिटी के फायदों की जरूरत होने पर इसका इस्तेमाल करें।

मीठे स्थान पर इसका प्रयोग करें। अन्य मामलों में, नहीं।


2
+1 - कोड के मानसिक मानचित्र को सरल बनाने का सरल प्रयास 5 अतुल्यकालिक संचालन को समानांतर रूप से पूरा करने के यादृच्छिक क्रम के साथ, अधिकांश लोगों को एक दिन के लिए पर्याप्त दर्द प्रदान करेगा। Async (और इसलिए स्वाभाविक रूप से समानांतर) कोड के व्यवहार के बारे में तर्क अच्छे पुराने सिंक्रोनस कोड की तुलना में बहुत कठिन है ...
अलेक्सई लेवेनकोव

2

मेरा मानना ​​है कि सभी तरीकों को एसिंक्स बनाने का एक अच्छा कारण है अगर उन्हें जरूरत नहीं है - एक्स्टेंसिबिलिटी। चयनात्मक बनाने के तरीके एसिंक्स केवल तभी काम करते हैं जब आपका कोड कभी भी विकसित नहीं होता है और आपको पता है कि ए (ए) हमेशा सीपीयू-बाउंड (आप इसे सिंक रखें) और विधि बी () हमेशा आई / ओ बाउंड है (आप इसे एसिंक्स चिह्नित करते हैं)।

लेकिन अगर चीजें बदल जाएं तो क्या होगा? हां, ए () गणना कर रहा है, लेकिन भविष्य में कुछ बिंदु पर आपको वहां लॉगिंग, या रिपोर्टिंग, या कार्यान्वयन के साथ उपयोगकर्ता-परिभाषित कॉलबैक जोड़ना होगा जो भविष्यवाणी नहीं कर सकता है, या एल्गोरिथ्म को बढ़ाया गया है और अब न केवल सीपीयू संगणनाएं शामिल हैं बल्कि कुछ I / O भी? आपको विधि को async में बदलने की आवश्यकता होगी, लेकिन यह एपीआई को तोड़ देगा और स्टैक अप के सभी कॉलर्स को भी अपडेट करने की आवश्यकता होगी (और वे विभिन्न विक्रेताओं से अलग-अलग ऐप भी हो सकते हैं)। या आपको सिंक संस्करण के साथ async संस्करण जोड़ने की आवश्यकता होगी, लेकिन इससे बहुत अंतर नहीं पड़ता - सिंक संस्करण का उपयोग करना अवरुद्ध हो जाएगा और इस प्रकार शायद ही स्वीकार्य हो।

यह बहुत अच्छा होगा अगर एपीआई को बदले बिना मौजूदा सिंक विधि को एसिंक्स बनाना संभव हो। लेकिन वास्तव में हमारे पास ऐसा कोई विकल्प नहीं है, मेरा मानना ​​है, और यदि यह वर्तमान में आवश्यक नहीं है, तो भी async संस्करण का उपयोग करना यह गारंटी देने का एकमात्र तरीका है कि आप भविष्य में कभी भी संगतता मुद्दों को नहीं मारेंगे।


हालांकि यह एक चरम टिप्पणी की तरह लगता है, इसमें बहुत सच्चाई है। सबसे पहले, इस मुद्दे के कारण: "यह एपीआई को तोड़ देगा और स्टैक अप के सभी कॉलर्स" async / प्रतीक्षा एक आवेदक को और अधिक कसकर युग्मित बनाता है। उदाहरण के लिए, यदि कोई उपवर्ग async / प्रतीक्षा का उपयोग करना चाहता है तो यह आसानी से Liskov प्रतिस्थापन सिद्धांत को तोड़ सकता है। इसके अलावा, एक ऐसी माइक्रोसॉफ़्ट आर्किटेक्चर की कल्पना करना मुश्किल है, जहां अधिकांश तरीकों के लिए एसक्यूएन / वेट की आवश्यकता न हो।
ItsAllABadJoke
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.