टास्क पर इंतजार क्यों नहीं किया जाता है।


101

इस कोड में:

private async void button1_Click(object sender, EventArgs e) {
    try {
        await Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2());
    }
    catch (Exception ex) {
        // Expect AggregateException, but got InvalidTimeZoneException
    }
}

Task DoLongThingAsyncEx1() {
    return Task.Run(() => { throw new InvalidTimeZoneException(); });
}

Task DoLongThingAsyncEx2() {
    return Task.Run(() => { throw new InvalidOperation();});
}

मुझे WhenAllएक बनाने और फेंकने की उम्मीद थी AggregateException, क्योंकि कम से कम एक कार्य के लिए यह अपवाद का इंतजार कर रहा था। इसके बजाय, मैं किसी एक कार्य द्वारा फेंके गए एकल अपवाद को वापस पा रहा हूं।

है WhenAllहमेशा एक नहीं बना AggregateException?


7
WhenAll है एक बनाने के AggregateException। आप का उपयोग किया है Task.Waitके बजाय awaitअपने उदाहरण में, तुम्हें पकड़ चाहते हैंAggregateException
पीटर रिची

2
+1, यह वही है जो मैं जानने की कोशिश कर रहा हूं, मुझे डिबगिंग और Google-ing के घंटे बचाएं।
केनीज़एक्स

काफी सालों में पहली बार मुझे सभी अपवादों की आवश्यकता थी Task.WhenAll, और मैं उसी जाल में पड़ गया। इसलिए मैंने इस व्यवहार के बारे में गहन विवरण में जाने की कोशिश की है।
noseratio

जवाबों:


75

मुझे बिल्कुल याद नहीं है कि कहां, लेकिन मैंने कहीं पढ़ा है कि नए async / प्रतीक्षा कीवर्ड के साथ, वे AggregateExceptionवास्तविक अपवाद में नहीं आते हैं।

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

यह भी मौजूदा कोड के आसान रूपांतरण के लिए आवश्यक था async / प्रतीक्षा का उपयोग करते हुए जहां बहुत सारे कोड विशिष्ट अपवादों की अपेक्षा करते हैं और समेकित अपवादों की नहीं।

- संपादित करें -

समझ गया:

बिल वैगनर द्वारा एक Async प्राइमर

बिल वैगनर ने कहा: ( जब एक्सेप्शन में हुआ )

... जब आप प्रतीक्षा का उपयोग करते हैं, तो संकलक द्वारा उत्पन्न कोड AggregateException को खोल देता है और अंतर्निहित अपवाद को फेंक देता है। प्रतीक्षा का लाभ उठाकर, आप कार्य द्वारा उपयोग किए गए AggregateException प्रकार को संभालने के लिए अतिरिक्त कार्य से बचते हैं। कार्य कक्षा में परिभाषित कार्य, Task.Wait और अन्य प्रतीक्षा विधियाँ। यह अंतर्निहित टास्क विधियों के बजाय प्रतीक्षा का उपयोग करने का एक और कारण है ...।


3
हाँ, मुझे पता है कि अपवाद से निपटने के लिए कुछ बदलाव हुए हैं, लेकिन टास्क के लिए नवीनतम डॉक्स। जब कोई भी राज्य "यदि कोई भी आपूर्ति किए गए कार्य एक दोषपूर्ण स्थिति में पूरा हो जाता है, तो लौटाया गया कार्य एक दोषपूर्ण स्थिति में भी पूरा होगा, जहां इसके अपवाद शामिल होंगे। आपूर्ति किए गए कार्यों में से प्रत्येक से अपरिवर्तित अपवाद के सेट का एकत्रीकरण ".... मेरे मामले में, मेरे दोनों कार्य एक दोषपूर्ण स्थिति में पूरे हो रहे हैं ...
माइकल रे लव्ट

4
@MichaelRayLovett: आप कहीं भी लौटाए गए कार्य को संग्रहीत नहीं कर रहे हैं। मैं शर्त लगाता हूं कि जब आप उस कार्य की अपवाद संपत्ति को देखते हैं, तो आपको एक एग्रिगेटएक्ससेप्शन मिलेगा। लेकिन, आपके कोड में, आप प्रतीक्षा कर रहे हैं। यह एग्रीगेट एक्ससेप्शन को वास्तविक अपवाद में अलिखित करता है।
साइक्लोन

3
मैंने सोचा था कि, भी, लेकिन दो समस्याएं सामने आईं: 1) मैं यह पता लगाने की कोशिश नहीं कर सकता कि कार्य को कैसे स्टोर किया जाए, इसलिए मैं इसकी जांच कर सकता हूं (यानी "टास्क मायटस्क = टास्क का इंतजार कर रहा हूं। सब (...)" doesn 't काम करने लगता है। और 2) मुझे लगता है कि मैं यह नहीं देखता कि इंतजार कभी एक अपवाद के रूप में कई अपवादों का प्रतिनिधित्व कैसे कर सकता है .. मुझे किस अपवाद की रिपोर्ट करनी चाहिए? यादृच्छिक पर एक उठाओ?
माइकल रे लव्ट

2
हां, जब मैं कार्य को संग्रहीत करता हूं और प्रतीक्षा की कोशिश / पकड़ में इसकी जांच करता हूं, तो मुझे लगता है कि यह अपवाद है। इसलिए मैंने जो डॉक्स पढ़े हैं वे सही हैं; Task.WhenAll एक AggregateException में अपवादों को लपेट रहा है। लेकिन फिर इंतजार उन्हें अलिखित कर रहा है। मैं आपके लेख को अभी पढ़ रहा हूं, लेकिन मुझे अभी तक यह नहीं दिखाई दिया है कि कैसे एग्रीगेटएक्सपेप्शन से एक अपवाद को उठाया जा सकता है और उस एक बनाम एक दूसरे को फेंक दिया जा सकता है
माइकल रे लव्ट

3
लेख पढ़ें, धन्यवाद। लेकिन मुझे अभी भी नहीं पता है कि प्रतीक्षित क्यों एक एकल अपवाद के रूप में एक एग्रीगेट एक्ससेप्शन (कई अपवादों का प्रतिनिधित्व) का प्रतिनिधित्व करता है। अपवादों की व्यापक हैंडलिंग कैसे होती है? .. मुझे लगता है कि अगर मैं यह जानना चाहता हूं कि कौन से कार्य अपवादों को फेंकते हैं और वे कौन से फेंक देते हैं, तो मुझे Task.WhenAll द्वारा बनाई गई टास्क ऑब्जेक्ट की जांच करनी होगी ??
माइकल रे लवेट

55

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

यह समाधान आपको समग्र अपवाद देता है (यानी सभी अपवाद जो विभिन्न कार्यों द्वारा फेंक दिए गए थे) और ब्लॉक नहीं करता है (वर्कफ़्लो अभी भी अतुल्यकालिक है)।

async Task Main()
{
    var task = Task.WhenAll(A(), B());

    try
    {
        var results = await task;
        Console.WriteLine(results);
    }
    catch (Exception)
    {
        if (task.Exception != null)
        {
            throw task.Exception;
        }
    }
}

public async Task<int> A()
{
    await Task.Delay(100);
    throw new Exception("A");
}

public async Task<int> B()
{
    await Task.Delay(100);
    throw new Exception("B");
}

कुंजी आपको प्रतीक्षा करने से पहले कुल कार्य के संदर्भ को बचाने के लिए है, तो आप इसकी अपवाद संपत्ति तक पहुंच सकते हैं जो आपके एग्रिगेटएक्ससेप्शन रखती है (भले ही केवल एक कार्य ने एक अपवाद फेंक दिया हो)।

आशा है कि यह अभी भी उपयोगी है। मुझे पता है मुझे आज यह समस्या थी।


उत्कृष्ट स्पष्ट उत्तर, यह IMO चयनित होना चाहिए।
बाईटदेव

3
+1, लेकिन क्या आप सीधे ब्लॉक के throw task.Exception;अंदर नहीं डाल सकते हैं catch? (यह मुझे एक खाली पकड़ देखने के लिए भ्रमित करता है जब अपवाद वास्तव में संभाले जा रहे हैं।)
AnorZaken

@AnorZaken बिल्कुल; मुझे याद नहीं है कि मैंने इसे मूल रूप से क्यों लिखा था, लेकिन मैं किसी भी नकारात्मक पहलू को नहीं देख सकता, इसलिए मैंने इसे पकड़ ब्लॉक में स्थानांतरित कर दिया है। धन्यवाद
Richiban

इस दृष्टिकोण का एक मामूली पहलू यह है कि रद्द करने की स्थिति ( Task.IsCanceled) ठीक से प्रचारित नहीं है। यह इस तरह से एक विस्तार सहायक का उपयोग करके हल किया जा सकता है ।
22

34

आप सभी कार्यों को देखने के लिए देख सकते हैं कि क्या एक से अधिक लोगों ने अपवाद छोड़ दिया है:

private async Task Example()
{
    var tasks = new [] { DoLongThingAsyncEx1(), DoLongThingAsyncEx2() };

    try 
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex) 
    {
        var exceptions = tasks.Where(t => t.Exception != null)
                              .Select(t => t.Exception);
    }
}

private Task DoLongThingAsyncEx1()
{
    return Task.Run(() => { throw new InvalidTimeZoneException(); });
}

private Task DoLongThingAsyncEx2()
{
    return Task.Run(() => { throw new InvalidOperationException(); });
}

2
यह काम नहीं करता। WhenAllपहले अपवाद पर बाहर निकलता है और वापस लौटता है। देखें: stackoverflow.com/questions/6123406/waitall-vs-whenall
jenson-button-घटना

14
पिछली दो टिप्पणियां गलत हैं। कोड वास्तव में काम करता है और exceptionsइसमें दोनों अपवाद शामिल हैं।
टोबियास

DoLongThingAsyncEx2 () को नए InvalidOperation () के बजाय नया InvalidOperationException ()
फेंकना होगा

8
यहां किसी भी संदेह को कम करने के लिए, मैंने एक विस्तारित फ़िडल को एक साथ रखा, जो उम्मीद करता है कि वास्तव में दिखाता है कि यह हैंडलिंग कैसे खेलता है: dotnetfiddle.net/X2AOvM । आप देख सकते हैं कि awaitपहले अपवाद को हटाने का कारण बनता है, लेकिन सभी अपवाद वास्तव में अभी भी कार्य सारणी के माध्यम से उपलब्ध हैं।
न्यूक्लियरिजॉन

13

मैंने सोचा था कि मैं @ रिचीबन के उत्तर पर विस्तार करूंगा, यह कहने के लिए कि आप इसे कार्य से संदर्भित करके एग्रिगेटएक्ससेप्शन को कैच ब्लॉक में भी संभाल सकते हैं। उदाहरण के लिए:

async Task Main()
{
    var task = Task.WhenAll(A(), B());

    try
    {
        var results = await task;
        Console.WriteLine(results);
    }
    catch (Exception ex)
    {
        // This doesn't fire until both tasks
        // are complete. I.e. so after 10 seconds
        // as per the second delay

        // The ex in this instance is the first
        // exception thrown, i.e. "A".
        var firstExceptionThrown = ex;

        // This aggregate contains both "A" and "B".
        var aggregateException = task.Exception;
    }
}

public async Task<int> A()
{
    await Task.Delay(100);
    throw new Exception("A");
}

public async Task<int> B()
{
    // Extra delay to make it clear that the await
    // waits for all tasks to complete, including
    // waiting for this exception.
    await Task.Delay(10000);
    throw new Exception("B");
}

11

आप सोच रहे हैं Task.WaitAll- यह एक फेंकता है AggregateException

जब सभी सिर्फ अपवादों की सूची के पहले अपवाद को फेंक देते हैं तो इसका सामना होता है।


3
यह गलत है, WhenAllविधि से लौटाए गए कार्य में एक Exceptionसंपत्ति होती है जो कि AggregateExceptionइसमें फेंके गए सभी अपवादों से युक्त होती है InnerExceptions। यहां जो हो रहा है, वह यह है कि awaitपहला आंतरिक अपवाद AggregateExceptionखुद के बजाय फेंकना (जैसे कि चक्रवात ने कहा)। Waitप्रतीक्षा के बजाय कार्य की विधि को कॉल करना मूल अपवाद को फेंकने का कारण बनता है।
Şफ़ाक़ गुर

3

यहाँ कई अच्छे जवाब हैं, लेकिन मैं अभी भी अपने शेख़ी को पोस्ट करना चाहूंगा क्योंकि मैं अभी भी उसी समस्या पर आया हूं और कुछ शोध किए हैं। या नीचे TLDR संस्करण पर जाएं।

समस्या

केवल taskद्वारा लौटाए गए प्रतीक्षा में संग्रहीत Task.WhenAllके पहले अपवाद को फेंकता AggregateExceptionहै task.Exception, भले ही कई कार्यों में गलती हो।

के लिए वर्तमान डॉक्सTask.WhenAll कहते हैं:

यदि आपूर्ति किए गए कार्यों में से कोई भी एक दोषपूर्ण स्थिति में पूरा होता है, तो लौटाया गया कार्य भी एक दोषपूर्ण स्थिति में पूरा होगा, जहां इसके अपवादों में आपूर्ति किए गए कार्यों में से प्रत्येक से अलिखित अपवादों के समूह का एकत्रीकरण होगा।

जो सही है, लेकिन यह लौटाए गए कार्य का इंतजार किए जाने के पूर्वोक्त "अलिखित" व्यवहार के बारे में कुछ नहीं कहता है।

मुझे लगता है, डॉक्स ने इसका उल्लेख नहीं किया है क्योंकि यह व्यवहार के लिए विशिष्ट नहीं हैTask.WhenAll

यह बस Task.Exceptionप्रकार का है AggregateExceptionऔर awaitनिरंतरता के लिए यह हमेशा अपने पहले आंतरिक अपवाद के रूप में अलिखित हो जाता है, डिजाइन द्वारा। यह ज्यादातर मामलों के लिए महान है, क्योंकि आमतौर परTask.Exception केवल एक आंतरिक अपवाद होता है। लेकिन इस कोड पर विचार करें:

Task WhenAllWrong()
{
    var tcs = new TaskCompletionSource<DBNull>();
    tcs.TrySetException(new Exception[]
    {
        new InvalidOperationException(),
        new DivideByZeroException()
    });
    return tcs.Task;
}

var task = WhenAllWrong();    
try
{
    await task;
}
catch (Exception exception)
{
    // task.Exception is an AggregateException with 2 inner exception 
    Assert.IsTrue(task.Exception.InnerExceptions.Count == 2);
    Assert.IsInstanceOfType(task.Exception.InnerExceptions[0], typeof(InvalidOperationException));
    Assert.IsInstanceOfType(task.Exception.InnerExceptions[1], typeof(DivideByZeroException));

    // However, the exception that we caught here is 
    // the first exception from the above InnerExceptions list:
    Assert.IsInstanceOfType(exception, typeof(InvalidOperationException));
    Assert.AreSame(exception, task.Exception.InnerExceptions[0]);
}

यहाँ, इसका एक उदाहरण AggregateExceptionइसके पहले आंतरिक अपवाद के लिए अलिखित हो जाता है InvalidOperationException, ठीक उसी तरह जैसे हम इसके साथ थे Task.WhenAllDivideByZeroExceptionअगर हम task.Exception.InnerExceptionsसीधे नहीं गए तो हम निरीक्षण करने में विफल हो सकते हैं ।

Microsoft के स्टीफन Toub संबंधित GitHub मुद्दे में इस व्यवहार के पीछे का कारण बताते हैं :

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

नोट करने के लिए एक और महत्वपूर्ण बात, यह अलिखित व्यवहार उथला है। यानी, यह केवल पहले अपवाद को खोल देगा AggregateException.InnerExceptionsऔर इसे वहां छोड़ देगा, भले ही यह दूसरे का उदाहरण हो AggregateException। इससे भ्रम की एक और परत जुड़ सकती है। उदाहरण के लिए, आइए WhenAllWrongइस तरह बदलें :

async Task WhenAllWrong()
{
    await Task.FromException(new AggregateException(
        new InvalidOperationException(),
        new DivideByZeroException()));
}

var task = WhenAllWrong();

try
{
    await task;
}
catch (Exception exception)
{
    // now, task.Exception is an AggregateException with 1 inner exception, 
    // which is itself an instance of AggregateException
    Assert.IsTrue(task.Exception.InnerExceptions.Count == 1);
    Assert.IsInstanceOfType(task.Exception.InnerExceptions[0], typeof(AggregateException));

    // And now the exception that we caught here is that inner AggregateException, 
    // which is also the same object we have thrown from WhenAllWrong:
    var aggregate = exception as AggregateException;
    Assert.IsNotNull(aggregate);
    Assert.AreSame(exception, task.Exception.InnerExceptions[0]);
    Assert.IsInstanceOfType(aggregate.InnerExceptions[0], typeof(InvalidOperationException));
    Assert.IsInstanceOfType(aggregate.InnerExceptions[1], typeof(DivideByZeroException));
}

एक समाधान (TLDR)

इसलिए, await Task.WhenAll(...)जो मैं व्यक्तिगत रूप से चाहता था, वह इसमें सक्षम है:

  • एक एकल अपवाद प्राप्त करें यदि केवल एक ही फेंक दिया गया हो;
  • AggregateExceptionयदि एक से अधिक अपवादों को सामूहिक रूप से एक या अधिक कार्यों द्वारा फेंका गया हो, तो प्राप्त करें ;
  • Taskइसकी जाँच के लिए केवल बचत करने से बचें Task.Exception;
  • रद्दीकरण की स्थिति को ठीक से प्रचारित करें ( Task.IsCanceled), जैसे कि कुछ ऐसा नहीं करेगा Task t = Task.WhenAll(...); try { await t; } catch { throw t.Exception; }:।

मैंने उसके लिए निम्नलिखित एक्सटेंशन को एक साथ रखा है:

public static class TaskExt 
{
    /// <summary>
    /// A workaround for getting all of AggregateException.InnerExceptions with try/await/catch
    /// </summary>
    public static Task WithAggregatedExceptions(this Task @this)
    {
        // using AggregateException.Flatten as a bonus
        return @this.ContinueWith(
            continuationFunction: anteTask =>
                anteTask.IsFaulted &&
                anteTask.Exception is AggregateException ex &&
                (ex.InnerExceptions.Count > 1 || ex.InnerException is AggregateException) ?
                Task.FromException(ex.Flatten()) : anteTask,
            cancellationToken: CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            scheduler: TaskScheduler.Default).Unwrap();
    }    
}

अब, निम्नलिखित उस तरीके से काम करता है जैसा मैं चाहता हूँ:

try
{
    await Task.WhenAll(
        Task.FromException(new InvalidOperationException()),
        Task.FromException(new DivideByZeroException()))
        .WithAggregatedExceptions();
}
catch (OperationCanceledException) 
{
    Trace.WriteLine("Canceled");
}
catch (AggregateException exception)
{
    Trace.WriteLine("2 or more exceptions");
    // Now the exception that we caught here is an AggregateException, 
    // with two inner exceptions:
    var aggregate = exception as AggregateException;
    Assert.IsNotNull(aggregate);
    Assert.IsInstanceOfType(aggregate.InnerExceptions[0], typeof(InvalidOperationException));
    Assert.IsInstanceOfType(aggregate.InnerExceptions[1], typeof(DivideByZeroException));
}
catch (Exception exception)
{
    Trace.WriteLine($"Just a single exception: ${exception.Message}");
}

2
शानदार जवाब
रोल

-3

यह मेरे लिए काम करता है

private async Task WhenAllWithExceptions(params Task[] tasks)
{
    var result = await Task.WhenAll(tasks);
    if (result.IsFaulted)
    {
                throw result.Exception;
    }
}

1
WhenAllजैसा है वैसा नहीं है WhenAnyawait Task.WhenAny(tasks)कोई भी कार्य पूरा होते ही पूरा हो जाएगा। इसलिए यदि आपके पास एक कार्य है जो तुरंत पूरा होता है और सफल होता है और दूसरा कोई अपवाद फेंकने से पहले कुछ सेकंड लेता है, तो यह बिना किसी त्रुटि के तुरंत वापस आ जाएगा।
स्ट्रिपिंगवर्यर

फिर फेंक लाइन यहाँ मारा कभी नहीं किया जाएगा - WhenAll अपवाद उत्पन्न होता है
thab

-5

आपके कोड में, http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/task-exception-handling-in-net-4-5 पर समझाया गया पहला अपवाद डिज़ाइन द्वारा दिया गया है । aspx

यदि आप इस तरह से कोड लिखते हैं, तो आपके प्रश्न के लिए, आपको AggreateException मिल जाएगी:

try {
    var result = Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2()).Result; 
}
catch (Exception ex) {
    // Expect AggregateException here
} 
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.