تذکر:
برای درک صحیح مطالب این مقاله، شما باید آشنایی و تجربه کافی با ملاحظات و محدودیت
های برنامه های مالتی ترید (Multi-thread) و همچنین مکانیزم های مدیریت خطا و استثنا
(Exception Handling) تحت پلتفرم دات نت را داشته باشید. قبلا مقاله ای در مورد مکانیزم
های مدیریت استثناء در برنامه های تحت پلتفرم دات نت در این سایت نوشته شده است که
مطالعه آن پیشنهاد می گردد (آموزش
کتابخانه Microsoft Enterprise Library 5.0 - قسمت ششم).
مقدمه:
تصور کنید که برنامه شما در حال استفاده از ترید (Thread) های مختلفی می باشد
و ناگهان خطایی در یکی از این ترید ها رخ می دهد. اکنون چه باید کرد؟ البته این موضوع
بستگی به نوع خطا و منطق برنامه دارد. گاهی اوقات فقط نیاز به لاگ نمودن اطلاعات می
باشد تا پشتیبان های برنامه در آینده خطای مورد نظر را بررسی نموده و در جهت رفع آن
اقدام نمایند.
اما همیشه سناریو به این سادگی نیست و گاهی نیاز است که ترید اصلی برنامه را از
رخداد این خطا مطلع گرداند تا اقدامات مقتضی صورت پذیرد. گاهی ممکن است نیاز داشته
باشیم تا تمامی خطاهای صورت گرفته در ترید های مختلف را جمع آوری نموده و در فرصت مناسب
به ترید اصلی برنامه ارائه کنیم!
خوشبختانه کلاس ExceptionDispatchInfo امکان جدیدی می باشد در فریم ورک دات نت نسخه
4.5 معرفی گردیده و کمک شایانی به حل این مشکل می کند.
آغاز:
به قطعه کد زیر توجه فرمایید.
class Program
{
static void Main()
{
var exceptions = new BlockingCollection<ExceptionDispatchInfo>();
//-----------Start Work in a different Thread
ThreadPool.QueueUserWorkItem(param =>
{
try
{
MethodOne(); // Line 23
}
catch (Exception ex)
{
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
exceptions.Add(exceptionDispatchInfo);
}
//exceptions.CompleteAdding();
});
//-----------Finish Work in a different Thread
//-----------Continue Work in the main Thread
foreach (var exceptionDispatchInfo in exceptions.GetConsumingEnumerable())
{
try
{
exceptionDispatchInfo.Throw(); // Line 40
}
catch (Exception ex)
{
Console.WriteLine("{0}", ex);
}
}
Console.ReadKey();
}
private static void MethodOne()
{
Console.WriteLine("Inside MethodOne");
MethodTwo(); // Line 54
}
private static void MethodTwo()
{
Console.WriteLine("Inside MethodTwo");
MethodThree(); // Line 61
}
private static void MethodThree()
{
Console.WriteLine("Inside MethodThree");
throw new NotImplementedException(); // Line 68
}
}
اکنون به بررسی قطعه کد بالا می پردازیم.
این برنامه یک برنامه ساده از نوع کنسول می باشد که جهت آموزش عملکرد کلاس
ExceptionDispatchInfo مورد استفاده قرار گرفته است.
در نقطه شروع برنامه یک شیء از نوع کلاس BlockingCollection ایجاد نموده ایم. این
کلاس جزو مجموعه کلاس هایی می باشد که در فضای نامی System.Collections.Concurrent
قرار دارند و اصطلاحا Thread-Safe می باشند یعنی می توانند بین ترید های مختلف به اشتراک
گذاشته شده و مورد استفاده قرار گیرند. کلاس BlockingCollection شباهت زیادی به کلاس
مشهور و محبوب List<T>دارد و می تواند برای کاربردهای مشابه در برنامه های مالتی
ترید مورد استفاده قرار گیرد.
سپس با استفاده از ThreadPool.QueueUserWorkItem قطعی کدی را در یک ترید مجزا اجرا
نموده ایم. داخل این ترید متد MethodOne را فراخوانی نموده ایم (توجه داشته باشید که
این عمل در داخل بلاک try/catch صورت گرفته است) که این متد در داخل خود متد MethodTwo
را فراخوانی نموده و این متد نیز در داخل خود MethodThree رو فرا می خوند. داخل
MethodThree به طور عمد یک خطا از نوع NotImplementedException ایجاد نموده ایم.
پس از رخداد این خطا، جریان کد به داخل بلاک catch منتقل می گردد. دقت داشته باشید
که این اعمال داخل یک ترید مجزا در حال انجام می باشند و نه ترید اصلی برنامه.
در اینجا ما خطای برنامه را با استفاده از فرمان
ExceptionDispatchInfo.Capture
دریافت نموده و به لیست خطاهای برنامه اضافه نموده ایم.
catch (Exception ex)
{
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
exceptions.Add(exceptionDispatchInfo);
}
exceptions.CompleteAdding();
سپس متد CompleteAdding که مربوط به کلاس BlockingCollection را فراخوانی نموده
ایم. فراخوانی این متد نشانگر این است کهما دیگر قصدی برای اضافه نمودن آیتم
های جدید به این کلاس را نداریم.
تذکر:
در مثال این مقاله ما فقط یه ترید جدید ایجاد نموده و پس از اضافه نمودن خطا به
لیست BlockingCollection متد CompleteAdding را فراخوانده ایم. پس از فراخوانی این
متد، دیگر امکان اضافه نمودن آیتم جدیدی به این لیست وجود ندارد و در صورت انجام
این کار با خطا روبرو خواهید شد. بنابراین اگر برنامه شما دارای ترید های متعدد می
باشد باید به شکلی برنامه را مدیریت کنید که این متد فقط یکبار و
پس از جمع آوری خطاهای تمامی ترید ها فراخوانی گردد (مگر اینکه نیاز مندی های
برنامه شما به شکل دیگری باشد). لازم به ذکر است که فراخوانی این متد برای سناریوی
مطرح شده توسط ما اصلا الزامی نیست ولی اگر در برنامه شما در حال مانیتور نمودن
عملکرد ترید ها باشید، با انجام این کار مشخص می کنید که عملکرد این لیست به پایان
رسیده است. کلاس BlockingCollection همچین دارای یک خصوصیت به نام
IsCompleted می باشد که پس از فراخوانی متد CompleteAdding مقدار آن برابر
true می
گردد.
اطلاعات بیشتر
اکنون ما به ترید اصلی برنامه بازگشته ایم و با استفاده از یک حلقه foreach به
محتویات کلاس BlockingCollection دسترسی پیدا نموده ایم.
//-----------Continue Work in the main Thread
foreach (var exceptionDispatchInfo in exceptions.GetConsumingEnumerable())
{
try
{
exceptionDispatchInfo.Throw(); // Line 40
}
catch (Exception ex)
{
Console.WriteLine("{0}", ex);
}
}
خوب قسمت جالب داستان فرا رسیده است. ما
تمامی خطاهای ثبت شده را این حلقه Throw می کنیم و محتویات خطاها را کنسول نمایش می
دهیم. توجه داشته باید که در مثال این مقاله ما فقط یک خطا به لیست BlockingCollection
اضافه نموده ایم که در متد
MethodThree رخ داده است.
برنامه را اجرا می کنیم و با خروجی مشابه
تصویر زیر مواجه می شویم.
همانطور که مشاهده می کنید اطلاعات خطای مربوطه (حتی StackTrace) به شکل کامل
حفظ شده است. قسمت های جالب این خطا با رنگ های متفاوت جدید درک بهتر، متمایز
شده اند.
برگرفته از: Net 4.5
C# using ExceptionDispatchInfo.Capture to rethrow exception
سورس مثال این مقاله از طریقه لینک بالای
صفحه قابل دانلود می باشد.