मुझे बहु-प्रतीक्षित ओवरों में सिंगल 'वेट टास्क.जेन ऑल' क्यों पसंद करना चाहिए?


127

यदि मुझे कार्य पूरा करने के आदेश की परवाह नहीं है और बस उन्हें पूरा करने की आवश्यकता है, तो क्या मुझे await Task.WhenAllकई के बजाय अभी भी उपयोग करना चाहिए await? जैसे, (और क्यों?) के DoWork2लिए एक पसंदीदा तरीका नीचे है DoWork1:

using System;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static async Task<string> DoTaskAsync(string name, int timeout)
        {
            var start = DateTime.Now;
            Console.WriteLine("Enter {0}, {1}", name, timeout);
            await Task.Delay(timeout);
            Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
            return name;
        }

        static async Task DoWork1()
        {
            var t1 = DoTaskAsync("t1.1", 3000);
            var t2 = DoTaskAsync("t1.2", 2000);
            var t3 = DoTaskAsync("t1.3", 1000);

            await t1; await t2; await t3;

            Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
        }

        static async Task DoWork2()
        {
            var t1 = DoTaskAsync("t2.1", 3000);
            var t2 = DoTaskAsync("t2.2", 2000);
            var t3 = DoTaskAsync("t2.3", 1000);

            await Task.WhenAll(t1, t2, t3);

            Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
        }


        static void Main(string[] args)
        {
            Task.WhenAll(DoWork1(), DoWork2()).Wait();
        }
    }
}

2
क्या होगा यदि आप वास्तव में नहीं जानते हैं कि समानांतर में आपको कितने कार्यों की आवश्यकता है? क्या होगा यदि आपके पास 1000 कार्य चलाने की आवश्यकता है? पहला वाला ज्यादा पठनीय नहीं होगा await t1; await t2; ....; await tn=> दूसरा दोनों मामलों में हमेशा सबसे अच्छा विकल्प होगा
cuongle

आपकी टिप्पणी समझ में आती है। मैं सिर्फ अपने लिए कुछ स्पष्ट करने की कोशिश कर रहा था, एक अन्य प्रश्न से संबंधित था जिसका मैंने हाल ही में उत्तर दिया था । उस स्थिति में, 3 कार्य थे।
avo

जवाबों:


113

हाँ, उपयोग करें WhenAll क्योंकि यह एक ही बार में सभी त्रुटियों का प्रचार करता है। यदि आप पहले से ही थ्रो का इंतजार कर रहे हैं, तो कई प्रतीक्षा के साथ, आप त्रुटियों को खो देते हैं।

एक और महत्वपूर्ण अंतर यह है कि विफलताओं की उपस्थिति (दोषपूर्ण या रद्द किए गए कार्यों) में भीWhenAll सभी कार्यों को पूरा करने के लिए इंतजार करना होगा । अनुक्रम में मैन्युअल रूप से प्रतीक्षारत अप्रत्याशित संगति का कारण होगा क्योंकि आपके प्रोग्राम का वह भाग जो प्रतीक्षा करना चाहता है वास्तव में जल्दी जारी रहेगा।

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


9
"क्योंकि यह एक ही बार में सभी त्रुटियों का प्रचार करता है" यदि आप awaitइसका परिणाम नहीं देखते हैं।
svick

2
टास्क के साथ अपवादों का प्रबंधन कैसे किया जाता है, इस सवाल के लिए, यह लेख इसके पीछे के तर्क के लिए एक त्वरित लेकिन अच्छी जानकारी देता है (और ऐसा सिर्फ इसलिए भी होता है कि कई प्रतीक्षा के विपरीत जब व्हॉट्सऐप के लाभों का एक गुजरता है): ब्लॉग .msdn.com / b / pfxteam / आर्काइव / 2011/09/28 / 10217876.aspx
Oskar Lindberg

5
@ ऑस्कर लिंडबर्ग ओपी सभी कार्यों को शुरू कर रहा है इससे पहले कि वह पहले का इंतजार कर रहा है। इसलिए वे समवर्ती रूप से चलते हैं। लिंक के लिए धन्यवाद।
यूएसआर

3
@usr मैं अभी भी यह जानने के लिए उत्सुक था कि जब सभी समान SynchronizationContext के संरक्षण की तरह चतुर बातें नहीं करते हैं, तो इसके लाभों को शब्दार्थ से अलग करने के लिए। मुझे कोई निर्णायक दस्तावेज नहीं मिला, लेकिन आईएल को देखते हुए प्ले में IAsyncStateMachine के अलग-अलग कार्यान्वयन हैं। मैं उस सब को अच्छी तरह से नहीं पढ़ता हूं, लेकिन जब बहुत कम से कम कुशल आईएल कोड उत्पन्न होता है। (किसी भी मामले में, यह तथ्य कि जब व्हेन का परिणाम मेरे लिए शामिल सभी कार्यों की स्थिति को दर्शाता है, ज्यादातर मामलों में इसे पसंद करने के लिए पर्याप्त कारण है।)
ऑस्कर लिंडबर्ग

17
एक और महत्वपूर्ण अंतर यह है कि जब सभी कार्य पूर्ण होने की प्रतीक्षा करेंगे, भले ही, जैसे, t1 या t2 एक अपवाद को फेंक दें या रद्द कर दें।
मैग्नस

28

मेरी समझ यह है कि Task.WhenAllकई लोगों को पसंद करने का मुख्य कारण awaitप्रदर्शन / कार्य "मंथन" है: DoWork1विधि इस तरह से कुछ करती है:

  • दिए गए संदर्भ से शुरू करें
  • संदर्भ सहेजें
  • t1 की प्रतीक्षा करें
  • मूल संदर्भ को पुनर्स्थापित करें
  • संदर्भ सहेजें
  • t2 के लिए प्रतीक्षा करें
  • मूल संदर्भ को पुनर्स्थापित करें
  • संदर्भ सहेजें
  • t3 के लिए प्रतीक्षा करें
  • मूल संदर्भ को पुनर्स्थापित करें

इसके विपरीत, DoWork2यह करता है:

  • दिए गए संदर्भ से शुरू करें
  • संदर्भ सहेजें
  • t1, t2 और t3 सभी की प्रतीक्षा करें
  • मूल संदर्भ को पुनर्स्थापित करें

क्या यह आपके विशेष मामले के लिए एक बड़ा पर्याप्त सौदा है, ज़ाहिर है, "संदर्भ-निर्भर" (दंड को क्षमा करें)।


4
आपको लगता है कि एक संदेश भेजने के लिए लगता है कि वह सिंक्रनाइज़ेशन संदर्भ महंगा है। यह वास्तव में नहीं है। आपके पास एक प्रतिनिधि है जो एक कतार में जुड़ जाता है, उस कतार को पढ़ा जाएगा और उस प्रतिनिधि को निष्पादित किया जाएगा। ओवरहेड जो यह जोड़ता है, ईमानदारी से बहुत छोटा है। यह कुछ भी नहीं है, लेकिन यह बड़ा भी नहीं है। जो भी async ऑपरेशंस हैं, उनका खर्च लगभग सभी मामलों में इस तरह के ओवरहेड को बौना कर देगा।
16

सहमत, यह सिर्फ एक ही कारण था जिसके बारे में मैं एक दूसरे को पसंद करने के बारे में सोच सकता था। ठीक है, इसके अलावा टास्क के साथ समानता। WitAll जहां धागा स्विचिंग एक अधिक महत्वपूर्ण लागत है।
मार्सेल पोपस्कु

1
@ सेर्वी जैसा कि मार्सेल बताते हैं कि वास्तव में निर्भर करता है। यदि आप उदाहरण के लिए सिद्धांत की बात के रूप में सभी db कार्यों पर प्रतीक्षा का उपयोग करते हैं, और वह db asp.net उदाहरण के रूप में एक ही मशीन पर बैठता है, तो ऐसे मामले हैं जिन्हें आप एक db हिट का इंतजार करेंगे जो कि इन-मेमोरी इन-इंडेक्स, सस्ता है कि तुल्यकालन स्विच और पिरोया फेरबदल की तुलना में। उस समय के परिदृश्य में व्हेल () के साथ एक महत्वपूर्ण समग्र जीत हो सकती है, इसलिए ... यह वास्तव में निर्भर करता है।
23

3
@ChrisMoschini कोई तरीका नहीं है कि DB क्वेरी, भले ही वह DB को सर्वर के समान मशीन पर बैठा रहा हो, एक संदेश पंप में कुछ प्रतिनिधियों को जोड़ने के ओवरहेड की तुलना में तेज़ होने वाला है। यह कि इन-मेमोरी क्वेरी अभी भी लगभग निश्चित रूप से काफी धीमी है।
14

यह भी ध्यान दें कि यदि t1 धीमा है और t2 और t3 तेज है - तो दूसरा तुरंत वापस आने का इंतजार करता है।
डेविड रेफेली

18

एक अतुल्यकालिक विधि को राज्य-मशीन के रूप में लागू किया जाता है। विधियों को लिखना संभव है, ताकि वे राज्य-मशीनों में संकलित न हों, इसे अक्सर फास्ट-ट्रैक async विधि के रूप में संदर्भित किया जाता है। इन्हें इस तरह लागू किया जा सकता है:

public Task DoSomethingAsync()
{
    return DoSomethingElseAsync();
}

Task.WhenAllइसका उपयोग करते समय इस फास्ट-ट्रैक कोड को बनाए रखना संभव है, जबकि यह सुनिश्चित करते हुए कि कॉलर सभी कार्यों को पूरा करने के लिए प्रतीक्षा करने में सक्षम है, जैसे:

public Task DoSomethingAsync()
{
    var t1 = DoTaskAsync("t2.1", 3000);
    var t2 = DoTaskAsync("t2.2", 2000);
    var t3 = DoTaskAsync("t2.3", 1000);

    return Task.WhenAll(t1, t2, t3);
}

7

(अस्वीकरण: यह जवाब लिया जाता है / इयान ग्रीफिथ 'TPL Async से प्रेरित पर पाठ्यक्रम Pluralsight )

व्हेल को पसंद करने का एक और कारण अपवाद हैंडलिंग है।

मान लें कि आपके DoWork तरीकों पर आपके पास एक पकड़ने वाला ब्लॉक था, और मान लें कि वे अलग-अलग DoTask तरीके बुला रहे थे:

static async Task DoWork1() // modified with try-catch
{
    try
    {
        var t1 = DoTask1Async("t1.1", 3000);
        var t2 = DoTask2Async("t1.2", 2000);
        var t3 = DoTask3Async("t1.3", 1000);

        await t1; await t2; await t3;

        Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
    }
    catch (Exception x)
    {
        // ...
    }

}

इस स्थिति में, यदि सभी 3 कार्य अपवादों को फेंक देते हैं, तो केवल पहले वाला पकड़ा जाएगा। कोई भी बाद का अपवाद खो जाएगा। Ie यदि t2 और t3 अपवाद को छोड़ दें, तो केवल t2 को ही पकड़ा जाएगा; आदि बाद के कार्य अपवाद अप्रतिष्ठित चले जाएंगे।

जब व्हॉट्सऐप में जैसे - यदि कोई या सभी कार्य में खराबी है, तो परिणामी कार्य में सभी अपवाद होंगे। प्रतीक्षित कीवर्ड अभी भी हमेशा पहला अपवाद फिर से फेंकता है। तो अन्य अपवाद अभी भी प्रभावी रूप से अप्रमाणित हैं। इसे दूर करने का एक तरीका यह है कि जब कार्य के बाद एक खाली निरंतरता को जोड़ा जाए और प्रतीक्षा को वहां रखा जाए। इस तरह यदि कार्य विफल हो जाता है, तो परिणाम सम्पूर्ण एग्रीगेट अपवाद को फेंक देगा:

static async Task DoWork2() //modified to catch all exceptions
{
    try
    {
        var t1 = DoTask1Async("t1.1", 3000);
        var t2 = DoTask2Async("t1.2", 2000);
        var t3 = DoTask3Async("t1.3", 1000);

        var t = Task.WhenAll(t1, t2, t3);
        await t.ContinueWith(x => { });

        Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2]));
    }
    catch (Exception x)
    {
        // ...
    }
}

6

इस सवाल के अन्य उत्तर तकनीकी कारणों की पेशकश करते हैं कि क्यों await Task.WhenAll(t1, t2, t3);पसंद किया जाता है। यह उत्तर अभी भी उसी निष्कर्ष पर आते हुए एक नरम पक्ष (जो @usr से दृष्टिकोण करता है) से इसे देखने का लक्ष्य रखेगा।

await Task.WhenAll(t1, t2, t3); एक अधिक कार्यात्मक दृष्टिकोण है, क्योंकि यह आशय की घोषणा करता है और परमाणु है।

साथ await t1; await t2; await t3;, वहाँ व्यक्ति के बीच कोड जोड़ने से एक टीम (या शायद अपने भविष्य स्वयं!) को रोकने के लिए कुछ भी नहीं है awaitबयान। ज़रूर, आपने इसे अनिवार्य रूप से पूरा करने के लिए एक पंक्ति में संकुचित कर दिया है, लेकिन इससे समस्या हल नहीं होती है। इसके अलावा, आमतौर पर कोड की एक दी गई लाइन पर कई बयानों को शामिल करने के लिए टीम सेटिंग में यह खराब रूप होता है, क्योंकि यह मानव आंखों को स्कैन करने के लिए स्रोत फ़ाइल को कठिन बना सकता है।

सीधे शब्दों में कहें तो await Task.WhenAll(t1, t2, t3);यह अधिक सुपाच्य है, क्योंकि यह आपके इरादे को और स्पष्ट रूप से बताता है और अजीबोगरीब बग्स के प्रति कम संवेदनशील होता है, जो कोड में अच्छी तरह से अर्थ अपडेट से बाहर आ सकते हैं, या यहां तक ​​कि सिर्फ विलय गलत हो गया है।

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