17-09-12, 01:01 PM
المؤقتات Timers
يمكن أيضًا للمؤقتات المتوفرة قي الدوت نت أن تتسبب في تسرب الذاكرة , وهناك حالتان مختلفتان حسب نوع المؤقت الذي نتعامل معه , لنلق نظرة على الفئة Timer الموجودة ضمن مجال الأسماء System.Timers , بالنظر إلى المثال التالي فإنه عند إنشاء نسخة منها سيتم استدعاء الإجراء Timer_Tick كل ثانية:
ولسوء الحظ , فإن هذا الكائن سيبقى عالق حتى ينتهي التطبيق ولن تتمكن GC من التقاطه والسبب أن الدوت نت نفسه يحتفظ بمرجع إلى جميع المؤقتات النشطة من النوع System.Timers.Timer من جهة , ومن جهة أخرى فإن كائن Timer نفسه مرتبط مع EventHandler الخاص بفئتك , وفي هذه الحالة لن يفيدك القيام ب Unsubscribing لل EventHandler وحده, بل عليك أيضًا استخدام الواجهة IDisposable واستدعاء الإجراء Dispose الخاص بالفئة Timer داخل الإجراء Dispose الخاص بفئتك. كما هو موضح في الكود التالي:
نفس الكلام السابق ينطبق على الفئة System.Windows.Forms.Timer .
الأمر مختلف بالنسبة للفئة System.Threading.Timer , فهو من نوع خاص, فالدوت نت لا تحتفظ بأي مراجع نحو المؤقتات النشطة من هذا النوع , ولكنها تحتفظ بال Callback Delegate الذي يتم تمريره لل Constructor وهذا يعني أنك إذا نسيت استدعاء الإجراء Dispose الخاص بالفئة Timer فسيتم ستدعاء ال Finalizer الخاص بها في أي لحظة ما يعني احتمال حدوث خطأ غير متوقع في سير عمل برنامجك بعد تدمير ال Timer , جرب المثال التالي في الوضع Release بدل الوضع Debug.
لاحظ أنه إذا قمت بتجربة الكود السابق فإن المؤقت لن يعمل ولو لمرة واحدة وسيتم عمل Finalizing له وجمعه عند استدعاء GC.Collect بشكل صريح. وحتى تتمكن من حل المشكلة لا بد من استخدام ال using Statement حتى تضمن أن GC لن يستطيع الوصول إلى المؤقت وجمعه. والاستدعاء الضمني للإجراء Dispose في نهاية ال using Block يضمن ذلك, ولكن استدعاء Dispose في هذه الحالة قد يطيل عمر المؤقت في الذاكرة!
معالجة مشاكل ال Memory Leaks:
أسهل طريقة يمكن من خلالها تجنب مشاكل تسرب الذاكرة من خلال قياس كمية الذاكرة المستهلكة طوال فترة كتابة البرنامج, يمكنك الحصول على قدر الذاكرة التي تستهلكها كائنات برنامجك باستخدام الإجراء GC.GetTotalMemory , حيث يمكن تمرير قيمة منطقية , فإن قمت بتمرير True فذلك سيجعل الGC تقوم بعمل Garbage Collecting أولاً:
إذا كنت تعمل على تطبيق ضخم وتعاني من مشكلة بسبب حدوث تسرب في الذاكرة , فقد تساعد الأداة windbg.exe على اكتشافها , هناك أيضأ بعض الأدوات التي تعتمد على واجهة رسومية تساعد على فهم مايحدث بالضبط مثل Microsoft CLR Profiler , و SciTech Memory Profiler , و RedGate ANTS Memory Profiler , وال CLR نفسه يحتوي على مجموعة من الأداوت المساعدة مثل WMI Counters التي تساعد على مراقبة الموارد Resources التي يستهلكها البرنامج.
يمكن أيضًا للمؤقتات المتوفرة قي الدوت نت أن تتسبب في تسرب الذاكرة , وهناك حالتان مختلفتان حسب نوع المؤقت الذي نتعامل معه , لنلق نظرة على الفئة Timer الموجودة ضمن مجال الأسماء System.Timers , بالنظر إلى المثال التالي فإنه عند إنشاء نسخة منها سيتم استدعاء الإجراء Timer_Tick كل ثانية:
كود :
[align=left][FONT=Consolas][FONT=Consolas][color=blue]class[/color] [color=#2b91af]TimerTest[/color][/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=#2b91af]Timer[/color] timer = [color=blue]new[/color] [color=#2b91af]Timer[/color] { Interval = 1000};[/FONT][/FONT][/align]
[align=left][FONT=Consolas][FONT=Consolas] [color=blue]private[/color] TimerTest()[/FONT][/FONT][FONT=Consolas]
[FONT=Consolas] {[/FONT]
[FONT=Consolas] [color=blue]this[/color].timer.Elapsed += Timer_Tick;[/FONT]
[FONT=Consolas] [color=blue]this[/color].timer.Start();[/FONT][/FONT][/align]
[FONT=Consolas]
[align=left][FONT=Consolas] }[/FONT][/align]
[align=left][FONT=Consolas] [color=blue]private[/color] [color=blue]void[/color] Timer_Tick([color=blue]object[/color] sender, [color=#2b91af]ElapsedEventArgs[/color] e)[/FONT]
[FONT=Consolas] {[/FONT]
[FONT=Consolas] [color=#2b91af]Console[/color].WriteLine([color=#a31515]"Tick!"[/color]);[/FONT]
[FONT=Consolas] }[/FONT]
[FONT=Consolas]}[/FONT][/align]
[/FONT]كود :
[align=left][FONT=Consolas][FONT=Consolas][color=blue]class[/color] [color=#2b91af]TimerTest[/color] : [color=#2b91af]IDisposable[/color] [/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=#2b91af]Timer[/color] timer = [color=blue]new[/color] [color=#2b91af]Timer[/color] { Interval = 1000};[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]bool[/color] disposed = [color=blue]false[/color];[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]private[/color] TimerTest()[/FONT][/FONT][FONT=Consolas]
[FONT=Consolas] {[/FONT]
[FONT=Consolas] [color=blue]this[/color].timer.Elapsed += Timer_Tick;[/FONT]
[FONT=Consolas] [color=blue]this[/color].timer.Start();[/FONT][/FONT][/align]
[FONT=Consolas]
[align=left][FONT=Consolas] }[/FONT][/align]
[align=left][FONT=Consolas] [color=blue]private[/color] [color=blue]void[/color] Timer_Tick([color=blue]object[/color] sender, [color=#2b91af]ElapsedEventArgs[/color] e)[/FONT]
[FONT=Consolas] {[/FONT]
[FONT=Consolas] [color=blue]if[/color] (disposed) [color=blue]throw[/color] [color=blue]new[/color] [color=#2b91af]ObjectDisposedException[/color](GetType().FullName);[/FONT]
[FONT=Consolas] [color=#2b91af]Console[/color].WriteLine([color=#a31515]"Tick!"[/color]);[/FONT]
[FONT=Consolas] }[/FONT][/align]
[align=left][FONT=Consolas] [color=blue]public[/color] [color=blue]void[/color] Dispose()[/FONT]
[FONT=Consolas] {[/FONT]
[FONT=Consolas] timer.Dispose();[/FONT]
timer.Elapsed -= Timer_Tick;
[FONT=Consolas] [color=blue]this[/color].disposed = [color=blue]true[/color];[/FONT]
[FONT=Consolas] }[/FONT]
[FONT=Consolas]}[/FONT][/align]
[/FONT]نفس الكلام السابق ينطبق على الفئة System.Windows.Forms.Timer .
الأمر مختلف بالنسبة للفئة System.Threading.Timer , فهو من نوع خاص, فالدوت نت لا تحتفظ بأي مراجع نحو المؤقتات النشطة من هذا النوع , ولكنها تحتفظ بال Callback Delegate الذي يتم تمريره لل Constructor وهذا يعني أنك إذا نسيت استدعاء الإجراء Dispose الخاص بالفئة Timer فسيتم ستدعاء ال Finalizer الخاص بها في أي لحظة ما يعني احتمال حدوث خطأ غير متوقع في سير عمل برنامجك بعد تدمير ال Timer , جرب المثال التالي في الوضع Release بدل الوضع Debug.
كود :
[align=left][FONT=Consolas][FONT=Consolas] [color=#2b91af]Timer[/color] t = [color=blue]new[/color] [color=#2b91af]Timer[/color]([color=blue]delegate[/color] { [color=#2b91af]Console[/color].WriteLine([color=#a31515]"Tick"[/color]); }, [color=blue]null[/color], 1000, 1000);[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=#2b91af]GC[/color].Collect();[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=#2b91af]Thread[/color].Sleep(10000);[/FONT][/FONT][/align]معالجة مشاكل ال Memory Leaks:
أسهل طريقة يمكن من خلالها تجنب مشاكل تسرب الذاكرة من خلال قياس كمية الذاكرة المستهلكة طوال فترة كتابة البرنامج, يمكنك الحصول على قدر الذاكرة التي تستهلكها كائنات برنامجك باستخدام الإجراء GC.GetTotalMemory , حيث يمكن تمرير قيمة منطقية , فإن قمت بتمرير True فذلك سيجعل الGC تقوم بعمل Garbage Collecting أولاً:
كود :
[align=left][FONT=Consolas][color=#2b91af][COLOR=#2b91af][FONT=Consolas]Console[/FONT][/color][FONT=Consolas].WriteLine([color=#2b91af]GC[/color].GetTotalMemory([color=blue]true[/color]));[/FONT][/COLOR][/FONT][/align]إذا كنت تعمل على تطبيق تستخدم فيه Unit Tests يمكنك القيام بعمليات التحقق من أن الذاكرة يتم تحريرها كما هو متوقع وإذا فشل أي تحقق Assertion فعليك التحقق عندها فقط من التغييرات الأخيرة التي قمت بها في تطبيقك.
إذا كنت تعمل على تطبيق ضخم وتعاني من مشكلة بسبب حدوث تسرب في الذاكرة , فقد تساعد الأداة windbg.exe على اكتشافها , هناك أيضأ بعض الأدوات التي تعتمد على واجهة رسومية تساعد على فهم مايحدث بالضبط مثل Microsoft CLR Profiler , و SciTech Memory Profiler , و RedGate ANTS Memory Profiler , وال CLR نفسه يحتوي على مجموعة من الأداوت المساعدة مثل WMI Counters التي تساعد على مراقبة الموارد Resources التي يستهلكها البرنامج.

