सवाल Async बनाम "पुराने async प्रतिनिधि" के साथ आग और भूल जाओ


मैं अपने पुराने अग्नि-और-भूल कॉल को एक नए वाक्यविन्यास के साथ बदलने की कोशिश कर रहा हूं, और अधिक सादगी की उम्मीद कर रहा हूं और ऐसा लगता है कि यह मुझे दूर कर रहा है। यहां एक उदाहरण दिया गया है

class Program
{
    static void DoIt(string entry) 
    { 
        Console.WriteLine("Message: " + entry);
    }

    static async void DoIt2(string entry)
    {
        await Task.Yield();
        Console.WriteLine("Message2: " + entry);
    }

    static void Main(string[] args)
    {
        // old way
        Action<string> async = DoIt;
        async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
        Console.WriteLine("old-way main thread invoker finished");
        // new way
        DoIt2("Test2");   
        Console.WriteLine("new-way main thread invoker finished");
        Console.ReadLine();
    }
}

दोनों दृष्टिकोण एक ही काम करते हैं, हालांकि मुझे लगता है कि मुझे क्या हासिल हुआ है (इसकी कोई आवश्यकता नहीं है EndInvoke और करीबी हैंडल, जो अभी भी थोड़ा बहस योग्य है) मैं इंतजार कर नए तरीके से हार रहा हूं Task.Yield(), जो वास्तव में एक-लाइनर जोड़ने के लिए सभी मौजूदा एसिंक एफ एंड एफ विधियों को फिर से लिखने की एक नई समस्या उत्पन्न करता है। क्या प्रदर्शन / सफाई के मामले में कुछ अदृश्य लाभ हैं?

अगर मैं पृष्ठभूमि विधि को संशोधित नहीं कर सकता तो मैं एसिंक को लागू करने के बारे में कैसे जाउंगा? मुझे लगता है कि कोई सीधा तरीका नहीं है, मुझे एक रैपर एसिंक विधि बनाना होगा जो कार्य का इंतजार करेगा। Run ()?

संपादित करें: अब मैं देखता हूं कि मुझे असली प्रश्न गुम हो सकते हैं। सवाल यह है: एक सिंक्रोनस विधि ए () को देखते हुए, मैं इसे अतुल्यकालिक रूप से कैसे उपयोग कर सकता हूं async/await एक समाधान के बिना आग और भूलने के तरीके में जो "पुराने तरीके" से अधिक जटिल है


44
2017-10-09 15:04


मूल


async / await वास्तव में सिंक्रोनस वर्कलोड को किसी अन्य थ्रेड पर ऑफ़लोड करने के लिए डिज़ाइन नहीं किया गया है। मैंने कुछ सुंदर परियोजनाओं में एसिंक / प्रतीक्षा का उपयोग नहीं किया है Thread.Yield दृष्टि में मैं इस कोड को async प्रतीक्षा दर्शन के दुरुपयोग के रूप में देखता हूं। यदि कोई async IO नहीं है, async / प्रतीक्षा शायद गलत समाधान है। - spender
मैं असहमत हूं, खासकर मेरे मामले में; बहुत ही शुरुआत में उपलब्ध प्रतिक्रिया प्राप्त करने के लिए पूर्ण प्रक्रिया की प्रतीक्षा करने के लिए http अनुरोधकर्ता को मजबूर करने का कोई ठोस कारण नहीं है। बाकी को सुरक्षित रूप से ऑफलोड किया जा सकता है। वास्तव में एकमात्र सवाल यह है कि async / मदद की प्रतीक्षा कर सकता है, खराब हो सकता है या इस परिदृश्य में बस अनुपयोगी है। मुझे स्वीकार करना होगा कि मेरे पास क्या विचार था इसके बारे में अलग-अलग विचार थे। - mmix
मैं इस बात से असहमत नहीं हूं कि काम को ऑफ़लोडिंग की आवश्यकता हो सकती है। मैं कह रहा हूँ कि उपयोग कर रहा हूँ async/await के साथ संयुक्त Task.Yield एक बुरा गंध है। का उपयोग करते हुए ThreadPool.QueueUserWorkItem यहां एक बेहतर फिट होगा। आखिरकार, यह वही है जो आप करने की कोशिश कर रहे हैं ... थ्रेडपूल को काम को एक बेहद कम कोड पदचिह्न के साथ भेजें, है ना? - spender
ओह ठीक। उचित टिप्पणी, मैंने आपके दावे को गलत समझा। मुझे लगता है कि मैंने सोचा कि एसिंक के साथ मैं सिर्फ एक विधि कॉल करूंगा और यह जादुई रूप से एक और थ्रेड पर शुरू होगा :)। विभिन्न दृष्टिकोणों के बारे में बात करते हुए, क्या किसी को तीनों के बीच तुलना की जानकारी है? ThreadPool.QueueUserWorkItem बनाम Task.Factory.StartNew बनाम delegate.BeginInvoke? अगर मैं बदलाव करने जा रहा हूं, तो मैं इसे सर्वोत्तम उपलब्ध तरीके से भी कर सकता हूं। - mmix


जवाब:


से बचें async void। यह त्रुटि प्रबंधन के आसपास मुश्किल semantics है; मुझे पता है कि कुछ लोग इसे "आग और भूल जाते हैं" कहते हैं, लेकिन मैं आमतौर पर वाक्यांश "आग और दुर्घटना" का उपयोग करता हूं।

सवाल यह है कि: एक सिंक्रोनस विधि ए () को देखते हुए, मैं इसे "पुराने तरीके" से अधिक जटिल समाधान के बिना अग्नि-और-भूलने के तरीके में एसिंक / प्रतीक्षा के रूप में अतुल्यकालिक रूप से कैसे कॉल कर सकता हूं

आपको जरूरत नहीं है async / await। बस इसे इस तरह कहते हैं:

Task.Run(A);

62
2017-10-09 16:06



क्या होगा यदि ए () में एसिंक विधि कॉल है? - Anthony Johnston
मेरा मतलब है, आप कार्य की प्रतीक्षा न करने के बारे में चेतावनियों से कैसे बचते हैं? - Anthony Johnston
यह चेतावनी वहां है क्योंकि एक में आग और भूल जाते हैं async विधि लगभग निश्चित रूप से एक गलती है। अगर तुम हो सकारात्मक सुनिश्चित करें कि आप यही करना चाहते हैं, आप परिणाम को एक अप्रयुक्त स्थानीय चर के रूप में असाइन कर सकते हैं: var _ = Task.Run(A); - Stephen Cleary
धन्यवाद स्टीफन, और, हाँ, आग और भूलना मैं चाहता हूं, यह एक सॉकेट स्वीकार लूप, async शून्य सही लगता है। यह देखते हुए कि विधि हमेशा अपवादों को सही तरीके से संभालती है (jaylee.org/post/2012/07/08/...) क्या आप सहमत होंगे या मैं सिर्फ एक अचार में जा रहा हूं? - Anthony Johnston
@ एंथनी जॉनस्टन: मेरा मतलब था बुला एक से आग और भूलने की विधि async विधि लगभग निश्चित रूप से एक गलती है। आपके मामले में, चूंकि आप हमेशा विधि के भीतर अपवादों को संभालते हैं, इसलिए इसमें थोड़ा अंतर होता है async Task तथा async void। मैं अभी भी थोड़ा और आगे दुबला होगा async Task, सिर्फ इसलिए कि async void मेरे लिए "घटना हैंडलर" का तात्पर्य है। - Stephen Cleary


जैसा कि अन्य उत्तरों में उल्लेख किया गया है, और इस उत्कृष्ट द्वारा ब्लॉग पोस्ट आप उपयोग से बचना चाहते हैं async void यूआई घटना हैंडलर के बाहर। यदि आप एक चाहते हैं सुरक्षित "आग और भूल जाओ" async विधि, इस पैटर्न का उपयोग करने पर विचार करें (@ReedCopsey को श्रेय; यह विधि वह है जिसे उसने चैट वार्तालाप में दिया था):

  1. के लिए एक विस्तार विधि बनाएँ Task। यह पारित चलाता है Task और किसी भी अपवाद को पकड़ता / लॉग करता है:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. हमेशा उपयोग करें Task अंदाज async उन्हें बनाते समय विधियां, कभी नहीं async void

  3. इस तरह से उन तरीकों को आमंत्रित करें:

    MyTaskAsyncMethod().FireAndForget();
    

आपको इसकी आवश्यकता नहीं है await यह (न ही यह उत्पन्न करेगा await चेतावनी)। यह किसी भी त्रुटि को संभालेगा सही ढंग से, और क्योंकि यह एकमात्र ऐसा स्थान है जिसे आपने कभी रखा है async void, आपको रखना याद रखना नहीं है try/catch हर जगह ब्लॉक करता है।

यह आपको विकल्प भी देता है नहीं का उपयोग करते हुए async यदि आप वास्तव में चाहते हैं तो "आग और भूलें" विधि के रूप में विधि await यह सामान्य रूप से।


31
2018-01-09 01:19



खैर, अगर मेरे पास कोई कार्य है, तो मैं इसे चलाऊंगा, नहीं? - mmix
@mmix यह निर्भर करता है, आप एक का उपयोग कर सकते हैं Task ऑब्जेक्ट करें और इसे चलाएं, लेकिन यह प्रतीक्षा / async का उपयोग नहीं कर रहा है। इस तरह आप प्रतीक्षा / async के साथ "आग और भूल" करते हैं। ध्यान दें कि यह बहुत है अधिक उपयोगी जब आप Async फ्रेमवर्क विधियों का आह्वान कर रहे हैं, और आप उन्हें "आग और भूल" तरीके से उपयोग करना चाहते हैं। - BradleyDotNET
नमस्ते, यह एक पुरानी पोस्ट है, लेकिन आम तौर पर यह विचार आग वस्तु प्राप्त करने के लिए भाषा "प्रवाह" तत्वों का उपयोग करना था और भूलना था, बिना कार्य वस्तु का स्पष्ट रूप से उपयोग किए। हम एक निष्कर्ष पर पहुंचे कि यह संभव नहीं है क्योंकि एसिंक को कॉल करने से यह संभव नहीं है जब तक कि यह इंतजार न हो जाए। अगर मेरे पास टास्क ऑब्जेक्ट है तो मैं बस चलाता हूं () - यह और यह आग और भूल जाएगा। - mmix
@mmix कोई समस्या नहीं, यह सिर्फ रीड कॉपसी के साथ हुई एक चर्चा में आया, और एक अलग सवाल में हमने उपयोग करने के बारे में चर्चा की async void आग लगाना और भूलना जहां मुझे इस सवाल की ओर इशारा किया गया था कि ऐसा क्यों नहीं किया जाए। मैं इसे उपयोग करने के लिए "सही" तरीका के रूप में जोड़ रहा था async void ऐसा करने के लिए। - BradleyDotNET
मैं एसिंक / प्रतीक्षा कीवर्ड के साथ आकर्षण को समझ नहीं पा रहा हूं - इंतजार का मुद्दा अग्नि-सरल-भूल-परिदृश्य परिदृश्य को सरल बनाना है? - Chris F Carroll


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

Task.Factory.StartNew(() => DoIt2("Test2"))

और तुम ठीक हो जाओगे।


15
2017-10-09 15:26



जितना अधिक मैं इसके साथ प्रयोग करता हूं उतना ही ऐसा लगता है। async केवल उन प्रक्रियाओं के लिए है जहां आपके पास एसिंक्रोनस कार्य से परिणामों पर सार्थक निरंतरता है। कोई निरंतरता की आवश्यकता नहीं, कोई समर्थन नहीं (टास्क.इल्ड () के अलावा)। मुझे लगता है कि मैं फिर से विपणन द्वारा sniped गया ... - mmix
विषय पर, किसी भी वास्तविक अंतर के बीच delegate.BeginInvoke तथा Task.Factory.StartNew? - mmix
कार्य का उपयोग करने में सबसे बड़ा अंतर @mmix यह है कि यदि कार्य में कोई अपवाद होता है, तो यह कार्य वस्तु के अंतिमकर्ता में फेंक दिया जाएगा, क्योंकि कार्य की ग़लत स्थिति को देखते हुए कुछ भी नहीं है। यदि आप TaskScheduler.UnobservedTaskException ईवेंट के लिए पंजीकरण नहीं करते हैं, तो यह संभावित रूप से आपके सामान्य अंतिम-रिसॉर्ट लॉगिंग विधियों को ट्रिगर किए बिना एक बुरा क्रैश का कारण बन सकता है। जीसी के फाइनलजर को चलाने के कारण तक दुर्घटनाग्रस्त होने का दुर्भाग्यपूर्ण साइड इफेक्ट भी होता है, जबकि एक आवंटित प्रतिनिधि अपवाद के तुरंत बाद ऐप को क्रैश कर देगा। - Dan Bryant
@DanBryant: यह .NET 4.5 में बदल गया है। UnobservedTaskException अब प्रक्रिया को दुर्घटनाग्रस्त नहीं करेगा; यदि आप इसे संभाल नहीं पाते हैं, तो अपवादों को चुपचाप अनदेखा कर दिया जाता है। - Stephen Cleary
मुझे पहले वैसे ही लगा; यह मुझे एक ले लिया लंबा उस डिजाइन की सराहना करने के लिए चारों ओर आने का समय। Taskभविष्य में आधारित कोड होगा asyncआधारित; इस नई दुनिया में, एक unobserved Task  है एक आग और भूल जाओ Task। यह पुराने व्यवहार से कहीं अधिक विफल-फास्ट दर्शन का उल्लंघन नहीं करता है। पुराना व्यवहार डिफ़ॉल्ट रूप से दुर्घटनाग्रस्त हो जाएगा क्योंकि कुछ त्रुटि हुई पहले कुछ अनिश्चित समय, इसलिए पुराना व्यवहार वैसे भी "असफल" नहीं था। - Stephen Cleary


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

मुख्य अंतर यह है कि इंतजार के बीच के ब्लॉक BeginInvoke की तुलना में आमंत्रित करने के समान हैं। यदि आपको BeginInvoke की तरह व्यवहार की आवश्यकता है, तो आप अगली एसिंक्रोनस विधि (कार्य को वापस कर सकते हैं) पर कॉल कर सकते हैं, फिर उस कोड के बाद तक वास्तव में लौटा कार्य का इंतजार न करें जिसे आप 'BeginInvoke' करना चाहते थे।

    public async void Method()
    {
        //Do UI stuff
        await SomeTaskAsync();
        //Do more UI stuff (as if called via Invoke from a thread)
        var nextTask = NextTaskAsync();
        //Do UI stuff while task is running (as if called via BeginInvoke from a thread)
        await nextTask;
    }

1
2017-10-09 15:16



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