यहाँ कई अच्छे जवाब हैं, लेकिन मैं अभी भी अपने शेख़ी को पोस्ट करना चाहूंगा क्योंकि मैं अभी भी उसी समस्या पर आया हूं और कुछ शोध किए हैं। या नीचे 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.WhenAll
। DivideByZeroException
अगर हम 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}");
}
AggregateException
। आप का उपयोग किया हैTask.Wait
के बजायawait
अपने उदाहरण में, तुम्हें पकड़ चाहते हैंAggregateException