17-09-12, 01:01 PM
مقال تقني: Memory Leaks In .NET
بيئة التطوير : جميع إصدارات الفيجوال ستوديو بدءًا من 2003
اللغة #C نفس ما سيرد في هذا الموضوع ينطبق على VB .
المستوى: متقدم (يفترض بقارء الموضوع أن يكون ملماً باساسيات الدوت نت ).
في اللغات الغير مدارة مثل C++ عليك أن تتذكر دائماً بأنه يجب عليك أن تتخلص من الذاكرة التي قمت بتخصيصها من أجل كائن معين عندما تصبح بدون حاجة إليه, وإلا فسيحدث ما يسمى بال Memory Leak أو تسرب الذاكرة , في عالم الدوت نت والمصادر المدارة, إمكانية حدوث ال Memory Leak شبه منعدمة في أغلب الأحيان , والفضل يعود إلى ال CLR وآلية إدارة الذاكرة التي يعتمدها من خلال ال Garbage Collection , ما يعني أن اي كائن يتم تخصيص مساحة من الذاكرة له يتم نحريرها فور انتهاء مدة حياة ذلك الكائن وحالما يصبح بدون أي References .لكن ما يحدث في التطبيقات التي تعمل باستمرار وحيث أن حجم الذاكرة المستهلكة يزداد كلما زادت فترة حياة ذلك التطبيق , فإنه من الممكن حدوث ال Memory Leaks . تحدث مشاكل الذاكرة هذه عادة بسبب الكائنات التي تبقى عالقة في الذاكرة من دون استخدامها داخل تطبيقك , مثال شائع عن مسببات ال Memory Leaks وهي ال MulticastDelegates وبالضبط ال EventHandlers فمثلا عندما تقوم بربط EventHndler من كائن A إلى EventHandler لكائن B ولم تقم بفصل هذا الارتباط فسيبقى الكائن B عالقاً في الذاكرة ولن تقوم GC بجمعه حتى عندما تسند القيمة null أو Nothing له لأنه لازال هناك مرجع من الكائن A يشير إلى الكائن B , والمثال التالي يوضح ذلك:
لنفترض بعد ذلك أنك قمت بإنشاء 2000 نسخة Instance من الفئة Client :
قد يخطر في بالك أنه بعد الانتهاء من استخدام ال 2000 كائن سيتم تدميرهم وتحرير الذاكرة التي كانو يشغلونها وهذا خاطئ , سيبقى الألفين كائن معلقين في الذاكرة والسبب ان كل منهم لا يزال يرتبط بالكائن server الخاص بالفئة Test عن طريق ال EventHandler حيث قمنا بإنشاء هذا الارتباط داخل الConstructor الخاص بالفئة Client ولكنا لم نقم بالتخلص من هذا الارتباط وذلك بعمل Unsubscribing لل Eventhandlers, وفي العادة يتم فصل هذا الارتباط عن طريق تحقيق الواجهة IDisposable للفئة Client ويكون ذلك داخل الإجراء Dispose كما هو موضح في الكود التالي:
الآن وباستخدام using Statement أو استدعاء الإجراء Dispose يمكنك التأكد من أنه سيتم فصل الارتباط وتحرير ال 2000 كائن في المرة التالية التي يتم فيها عمل Garbage Collecting للذاكرة بعد الانتهاء من ال using Block كما يوضح المثال التالي:
وبالرغم من ذلك فإن هذا الحل لن يكون الأفضل فقد تضطر لعدم استخدام الواجهة Idisposable عندها لابد من استخدام الفئة WeakReference . سنتطرق إلى هذا الحل لاحقاً إن شاء الله.
بيئة التطوير : جميع إصدارات الفيجوال ستوديو بدءًا من 2003
اللغة #C نفس ما سيرد في هذا الموضوع ينطبق على VB .
المستوى: متقدم (يفترض بقارء الموضوع أن يكون ملماً باساسيات الدوت نت ).
في اللغات الغير مدارة مثل C++ عليك أن تتذكر دائماً بأنه يجب عليك أن تتخلص من الذاكرة التي قمت بتخصيصها من أجل كائن معين عندما تصبح بدون حاجة إليه, وإلا فسيحدث ما يسمى بال Memory Leak أو تسرب الذاكرة , في عالم الدوت نت والمصادر المدارة, إمكانية حدوث ال Memory Leak شبه منعدمة في أغلب الأحيان , والفضل يعود إلى ال CLR وآلية إدارة الذاكرة التي يعتمدها من خلال ال Garbage Collection , ما يعني أن اي كائن يتم تخصيص مساحة من الذاكرة له يتم نحريرها فور انتهاء مدة حياة ذلك الكائن وحالما يصبح بدون أي References .لكن ما يحدث في التطبيقات التي تعمل باستمرار وحيث أن حجم الذاكرة المستهلكة يزداد كلما زادت فترة حياة ذلك التطبيق , فإنه من الممكن حدوث ال Memory Leaks . تحدث مشاكل الذاكرة هذه عادة بسبب الكائنات التي تبقى عالقة في الذاكرة من دون استخدامها داخل تطبيقك , مثال شائع عن مسببات ال Memory Leaks وهي ال MulticastDelegates وبالضبط ال EventHandlers فمثلا عندما تقوم بربط EventHndler من كائن A إلى EventHandler لكائن B ولم تقم بفصل هذا الارتباط فسيبقى الكائن B عالقاً في الذاكرة ولن تقوم GC بجمعه حتى عندما تسند القيمة null أو Nothing له لأنه لازال هناك مرجع من الكائن A يشير إلى الكائن B , والمثال التالي يوضح ذلك:
كود :
[align=left][FONT=Consolas][FONT=Consolas][color=blue]public[/color] [color=blue]class[/color] [color=#2b91af]Server[/color][/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=blue]public[/color] [color=#2b91af]EventHandler[/color] handler;[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]}[/FONT][/FONT][/align]
[align=left][FONT=Consolas][FONT=Consolas][color=blue]public[/color] [color=blue]class[/color] [color=#2b91af]Client[/color] [/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=#2b91af]Server[/color] _server = [color=blue]null[/color];[/FONT][/FONT][/align]
[FONT=Consolas]
[align=left][FONT=Consolas][color=blue]public[/color] Client([color=#2b91af]Server[/color] server)[/FONT]
[FONT=Consolas]{[/FONT]
[FONT=Consolas] [color=blue]this[/color]._server = server;[/FONT]
[FONT=Consolas] [color=blue]this[/color]._server.handler += [color=blue]new[/color] [color=#2b91af]EventHandler[/color](Handle);[/FONT]
[FONT=Consolas]}[/FONT][/align]
[align=left][FONT=Consolas][color=blue]private[/color] [color=blue]void[/color] Handle([color=blue]object[/color] sender, [color=#2b91af]EventArgs[/color] e)[/FONT]
[FONT=Consolas]{[/FONT]
[FONT=Consolas] [color=green]// Do something[/color][/FONT]
[FONT=Consolas]}[/FONT]
[FONT=Consolas]}[/FONT][/align]
[/FONT]
لنفترض بعد ذلك أنك قمت بإنشاء 2000 نسخة Instance من الفئة Client :
كود :
[align=left][FONT=Consolas][FONT=Consolas][color=blue]class[/color] [color=#2b91af]Test[/color][/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=#2b91af]Server[/color] server = [color=blue]new[/color] [color=#2b91af]Server[/color]();[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=blue]public[/color] Test()[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]for[/color] ([color=blue]int[/color] i = 0; i < 2000; i++)[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] {[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]var[/color] client = [color=blue]new[/color] [color=#2b91af]Client[/color]([color=blue]this[/color].server);[/FONT][/FONT][FONT=Consolas]
[FONT=Consolas] }[/FONT][/FONT][/align]
[FONT=Consolas]
[align=left][FONT=Consolas]}[/FONT]
[FONT=Consolas]}[/FONT][/align]
[align=left][FONT=Consolas]}[/FONT][/align]
[/FONT]
كود :
[align=left][FONT=Consolas][FONT=Consolas][color=blue]public[/color] [color=blue]class[/color] [color=#2b91af]Client[/color] : [color=#2b91af]IDisposable[/color] [/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=#2b91af]Server[/color] _server = [color=blue]null[/color];[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=blue]bool[/color] disposed = [color=blue]false[/color];[/FONT][/FONT][/align]
[align=left][FONT=Consolas][FONT=Consolas][color=blue]public[/color] Client([color=#2b91af]Server[/color] server)[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]this[/color]._server = server;[/FONT][/FONT][FONT=Consolas]
[FONT=Consolas] [color=blue]this[/color]._server.handler += [color=blue]new[/color] [color=#2b91af]EventHandler[/color](Handle);[/FONT]
[FONT=Consolas]}[/FONT][/FONT][/align]
[FONT=Consolas]
[align=left][FONT=Consolas][color=blue]private[/color] [color=blue]void[/color] Handle([color=blue]object[/color] sender, [color=#2b91af]EventArgs[/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]([color=#a31515]"client"[/color]);[/FONT]
[FONT=Consolas] [color=green]// Do something[/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] [color=blue]this[/color]._server.handler -= [color=blue]new[/color] [color=#2b91af]EventHandler[/color](Handle);[/FONT]
[FONT=Consolas] [color=blue]this[/color].disposed = [color=blue]true[/color];[/FONT]
[FONT=Consolas]}[/FONT]
[FONT=Consolas]}[/FONT][/align]
[/FONT]
الآن وباستخدام using Statement أو استدعاء الإجراء Dispose يمكنك التأكد من أنه سيتم فصل الارتباط وتحرير ال 2000 كائن في المرة التالية التي يتم فيها عمل Garbage Collecting للذاكرة بعد الانتهاء من ال using Block كما يوضح المثال التالي:
كود :
[align=left][FONT=Consolas][FONT=Consolas][color=blue]class[/color] [color=#2b91af]Test[/color][/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=#2b91af]Server[/color] server = [color=blue]new[/color] [color=#2b91af]Server[/color]();[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas][color=blue]public[/color] Test()[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas]{[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]for[/color] ([color=blue]int[/color] i = 0; i < 2000; i++)[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] {[/FONT][/FONT]
[FONT=Consolas][FONT=Consolas] [color=blue]using[/color] ([color=blue]var[/color] client = [color=blue]new[/color] [color=#2b91af]Client[/color]([color=blue]this[/color].server))[/FONT][/FONT][FONT=Consolas]
[FONT=Consolas] {[/FONT]
[FONT=Consolas] [color=green]// Do something...[/color][/FONT]
[FONT=Consolas] }[/FONT]
[FONT=Consolas] }[/FONT][/FONT][/align]
[FONT=Consolas]
[align=left][FONT=Consolas]}[/FONT]
[FONT=Consolas]}[/FONT][/align]
[/FONT]
وبالرغم من ذلك فإن هذا الحل لن يكون الأفضل فقد تضطر لعدم استخدام الواجهة Idisposable عندها لابد من استخدام الفئة WeakReference . سنتطرق إلى هذا الحل لاحقاً إن شاء الله.