এখানে অনেক ভাল উত্তর, তবে আমি এখনও আমার রেন্ট পোস্ট করতে চাই কারণ আমি ঠিক একই সমস্যাটি পেরিয়ে কিছু গবেষণা চালিয়েছি। অথবা নীচের টিএলডিআর সংস্করণে যান।
সমস্যাটি
অপেক্ষা taskদ্বারা ফিরে Task.WhenAllশুধুমাত্র প্রথম ব্যতিক্রম ছোঁড়ার AggregateExceptionসঞ্চিত task.Exception, এমনকি যখন একাধিক কর্ম faulted হয়েছে।
জন্য বর্তমান ডক্সTask.WhenAll বলে:
সরবরাহকৃত কোনও কাজ যদি একটি ত্রুটিযুক্ত অবস্থায় সম্পূর্ণ হয় তবে প্রত্যাবর্তিত টাস্কটিও একটি ত্রুটিযুক্ত অবস্থায় সম্পূর্ণ হবে, যেখানে এর ব্যতিক্রমগুলিতে সরবরাহিত প্রতিটি কার্য থেকে আন-র্যাপযুক্ত ব্যতিক্রমগুলির সংশ্লেষণ থাকবে।
যা সঠিক, তবে কখন প্রত্যাবর্তিত কাজটির অপেক্ষায় রয়েছে তার উপরের উল্লিখিত "আনারপ্যাটিং" আচরণ সম্পর্কে কিছুই বলে না।
আমি মনে করি, দস্তাবেজগুলি এটি উল্লেখ করে না কারণ আচরণটি সুনির্দিষ্ট নয়Task.WhenAll ।
এটা যে কেবল হয় Task.Exceptionধরনের হয় AggregateExceptionএবং জন্য awaitcontinuations এটা সবসময় তার প্রথম ভেতরের ব্যতিক্রম হিসাবে unwrapped পরার নকশা। এটি বেশিরভাগ ক্ষেত্রেই দুর্দান্ত, কারণ সাধারণত 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)
{
Assert.IsTrue(task.Exception.InnerExceptions.Count == 2);
Assert.IsInstanceOfType(task.Exception.InnerExceptions[0], typeof(InvalidOperationException));
Assert.IsInstanceOfType(task.Exception.InnerExceptions[1], typeof(DivideByZeroException));
Assert.IsInstanceOfType(exception, typeof(InvalidOperationException));
Assert.AreSame(exception, task.Exception.InnerExceptions[0]);
}
এখানে, উদাহরণটি একেবারে ঠিক একইভাবে AggregateExceptionতার প্রথম অভ্যন্তরীণ ব্যতিক্রমকে আবদ্ধ করা যায় InvalidOperationExceptionযা আমরা এটির সাথে থাকতে পারি Task.WhenAll। আমরা সরাসরি DivideByZeroExceptionযেতে না পারলে আমরা পর্যবেক্ষণ করতে ব্যর্থ হতে পারতাম task.Exception.InnerExceptions।
মাইক্রোসফ্টের স্টিফেন টাব সম্পর্কিত গিটহাব ইস্যুতে এই আচরণের পেছনের কারণ ব্যাখ্যা করেছেন :
আমি যে বিষয়টিটি দেখার চেষ্টা করছিলাম তা হ'ল এটি বহু বছর আগে যখন এগুলি মূলত যুক্ত হয়েছিল তখন গভীরভাবে আলোচনা করা হয়েছিল। আমরা প্রথমে আপনার পরামর্শ অনুসারে এটি করেছি, যখন সমস্ত ব্যতিক্রম সমেত একক সমষ্টিগত এক্সসেপশন সম্বলিত টাস্কটি দিয়ে টাস্কটি ফিরে এসেছিল, যেমন টাস্ক x এক্সসেপশন এমন একটি সমষ্টিগত এক্সেরসেপশন রিটার্ন প্রত্যাবর্তন করবে যার মধ্যে প্রকৃত ব্যতিক্রম রয়েছে; তারপরে যখন এটি অপেক্ষা করা হত, তখন অভ্যন্তরীণ সমষ্টিগত ধারণাটি প্রচার করা হত। নকশা পরিবর্তন করার জন্য আমরা যে দৃ feedback় প্রতিক্রিয়া পেয়েছি তা হ'ল ক) এই জাতীয় ক্ষেত্রে বেশিরভাগ ক্ষেত্রে মোটামুটি একজাতীয় ব্যতিক্রম ছিল, যেমন সামগ্রিকভাবে সমস্ত প্রচার করা যে গুরুত্বপূর্ণ ছিল না, খ) সমষ্টি প্রচারের পরে ক্যাচের আশেপাশে প্রত্যাশা ভেঙে দেওয়া হয়েছিল নির্দিষ্ট ব্যতিক্রম ধরণের জন্য, এবং গ) যেসব ক্ষেত্রে কেউ সামগ্রিকতা চান, সে ক্ষেত্রে তারা আমার মতো দুটি লাইন দিয়ে স্পষ্টভাবে তা করতে পারে। আমাদের একাধিক ব্যতিক্রমযুক্ত কাজের ক্ষেত্রে অপেক্ষার আচরণটি কীভাবে নষ্ট হবে তা নিয়েও আমরা ব্যাপক আলোচনা করেছি এবং আমরা এখানেই পৌঁছেছি।
অন্য একটি গুরুত্বপূর্ণ বিষয় লক্ষণীয়, এই অনাবৃত আচরণ অগভীর। 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)
{
Assert.IsTrue(task.Exception.InnerExceptions.Count == 1);
Assert.IsInstanceOfType(task.Exception.InnerExceptions[0], typeof(AggregateException));
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));
}
একটি সমাধান (টিএলডিআর)
সুতরাং, ফিরে await Task.WhenAll(...), আমি ব্যক্তিগতভাবে যা চেয়েছিলাম তা হ'ল সক্ষম হ'ল:
- যদি কেবল একজনকে ফেলে দেওয়া হয় তবে একটি একক ব্যতিক্রম পান;
AggregateExceptionএক বা একাধিক কার্য সম্মিলিতভাবে একাধিক ব্যতিক্রম ছুঁড়ে দেওয়া থাকলে একটি পান ;
- এটি
Taskযাচাই করার জন্য কেবল সংরক্ষণ করতে এড়িয়ে চলুন Task.Exception;
- বাতিলের অবস্থা সঠিকভাবে (সঞ্চারিত
Task.IsCanceled), ভালো কিছু যে না করবে হিসাবে: Task t = Task.WhenAll(...); try { await t; } catch { throw t.Exception; }।
আমি এর জন্য নিম্নলিখিত এক্সটেনশন একসাথে রেখেছি:
public static class TaskExt
{
public static Task WithAggregatedExceptions(this Task @this)
{
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");
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