PDA

View Full Version : حرفه ای: dispose کردن یک شی



esafb52
چهارشنبه 06 شهریور 1392, 22:52 عصر
با سلام به همه اساتید
من یه کد نوشتم و از اشیایی استفاده کردم و در آخر اونا رو dispose کردم اما باز کد آنالیزر ویژوال استدیو رو که اجرا میکنم میگه برای این اشیا اینترفیس Idispose رو پیاده سازی کن
کدهای من
DataTable dt = new DataTable();
SqlDataAdapter dataAdapter = new SqlDataAdapter("loaddall", connection);
dataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
dataAdapter.Fill(dt);
dataGridView1.DataSource = dt;
dt.Dispose();
dataAdapter.Dispose();

حتی این کد رو به این صورت نوشتم اما باز همون هشدار ها رو میده
DataTable dt = new DataTable();
SqlDataAdapter dataAdapter = new SqlDataAdapter("loaddall", connection);
dataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
dataAdapter.Fill(dt);
dataGridView1.DataSource = dt;
dt.Dispose();
dataAdapter.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

آیا من نکته ای رو رعایت نمیکنم یا این گیر میده>؟!!!!!!!!!!!
هشدار"Warning 1 CA2000 : Microsoft.Reliability : In method object 'dataAdapter' is not disposed along all exception paths. Call System.IDisposable.Dispose on object 'dataAdapter' before all references to it are out of scope.
"

وقتی هم که از بلاک یوزینگ استفاده میکنم خب یک شی بیشتر که نمیشه تعریف کرد؟!!!

مهرداد صفا
پنج شنبه 07 شهریور 1392, 01:29 صبح
وقتی هم که از بلاک یوزینگ استفاده میکنم خب یک شی بیشتر که نمیشه تعریف کرد؟!!!

سلام.
به این صورت عمل کنید:

using (DataTable table2 = new DataTable())
{
using (SqlDataAdapter adapter2 = new SqlDataAdapter("select * from sys.databases", "server=.\\sqlexpress;integrated security=true;"))
{
adapter2.Fill(table2);
dataGridView1.DataSource = table2;
}
}

esafb52
پنج شنبه 07 شهریور 1392, 01:44 صبح
ولی چرا اون Dispose ها عمل نمیکنن اگر هم عمل میکنن پس اون پیام دلیلش چیه؟
چطور میشه حافظه اختصاص یافته به یک شی رو دید که چقدر است
ممنون

مهرداد صفا
پنج شنبه 07 شهریور 1392, 02:40 صبح
ولی چرا اون Dispose ها عمل نمیکنن اگر هم عمل میکنن پس اون پیام دلیلش چیه؟

شاید به این دلیل که ممکن است قبل از اینکه برنامه به خط مربوط به dispose برسد exception اتفاق بیفتد. در چنین حالتی استفاده از using باعث می شود object به طور خودکار dispose شود.


چطور میشه حافظه اختصاص یافته به یک شی رو دید که چقدر است

لطفا در یک تاپیک مجزا مطرح کنید.

esafb52
پنج شنبه 07 شهریور 1392, 02:54 صبح
به این صورت نوشتم حل شد میذارم اینجا دوستان هم استفاده کنند
try
{
dt = new DataTable();
dataAdapter = new SqlDataAdapter("loaddall", connection);
dataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
dataAdapter.Fill(dt);
dataGridView1.DataSource = dt;
}
finally
{
dt.Dispose();
dataAdapter.Dispose();
}

FastCode
پنج شنبه 07 شهریور 1392, 03:19 صبح
لطفا GC.Collect(); رو بدون اینکه حتی یک خط راجع بهش تحقیق کرده باشید صدا نزنید.اونکاری که فکر میکنی رو نمیکنه.

esafb52
پنج شنبه 07 شهریور 1392, 03:54 صبح
با سلام جناب استاد فست کد اتفاقا یه مقدار راجبش خوندم مفدترینش چیزهایی که من خوندم اینا هستن

وقتی شما Object ای رو new می کنید، باعث میشید تا ctor اون Object فراخوانی بشه. اما در مورد destructor ها وقتی اون object رو null میذارید، destructor فراخوانی نمیشه. در واقع شما کنترلی روی زمان فراخوانی اون ندارید. destructor وقتی فراخوانی میشه که GC صلاح بدونه. اما GC چی هستش؟ Garbage Collector یا همون GC، مدیریت تخصیص و آزادسازی حافظه یه برنامه رو در .NET به عهده داره. همه چیز در heap مدیریت شده رخ میده، heap ای که بسیار شبیه Heap کدهای native هستش با این تفاوت که Object ها توسط برنامه نویس آزاد نمیشن، بلکه هنگامی آزاد میشن که دیگه نیازی بهشون نیست. (بد نیست بدونید که GC در درون خودش حاوی pointer به object هایی هستش که تو heap مدیریت شده نگه داشته میشه. به این pointer ها root میگن. وقتی برنامه شروع به کار میکنه یا هر وقت شما Object ای رو new می کنید، CLR یه graph از root ها رو میگیره و تو یه زمان از پیش تعیین شده شروع به آزاد سازی همه اون object هایی میکنه که root اونها null شده. هر وقت شما یه object ای رو که new کردین، مساوی null بذارین، سیستم تشخصی میده و root مربوط به اون object رو هم null میکنه تا بعدا بتونه سر فرصت اونهایی که null هستن رو، release کنه).

اما سیستم از کجا می خواد بفهمه که دیگه نیازی به یه Object نیست؟ برای فهمیدن جواب این سوال ایتدا باید بدونیم که C#‎ دو نوع عمومی از type ها رو پشتیبانی میکنه: value type ها و Reference Type ها.

value-type ها تایپهای enum، built-in ها و struct ها هستن. اونها میتونند مستقیم مقادیر رو در خودشون حفظ کنن. Stack، حافظه ای هستش که این نوع تایپها توسط سیستم در اون نگهداری میشن. در مقابل reference-type ها رو داریم، که در واقع reference یا pointer به یک instance رو در stack نگه میدارن و خود instance رو در heap مدیریت شده. البته من قبلا در مورد value-type ها و reference ها در این پست (http://barnamenevis.org/showpost.php?p=370112&postcount=3) توضیحاتی داده بودم که خوندنش خالی از لطف نیست.

اما چه اتفاقی می افته وقتی شما یه Object ای رو new می کنید؟ CLR فرض میکنه که حافظه سیستم نامحدوده و شروع به تخصیص حافظه بصورت ترتیبی می کنه. یعنی خونه به خونه حافظه رو میره جلو و اختصاص میده به Object هایی که شما new می کنید. اما واقعیت اینه که سیستم، حافظه محدودی داره و تا ابد نمیتونه این مساله ادامه پیدا کنه، پس یه نفر باید فضاهای بلا استفاده تو heap رو آزاد کنه و اینجاست که GC توسط root هایی که بالا توضیح دادم، اقدام به آزادسازی حافظه می کنه. اما هنوز یه کار دیگه هم باید انجام بشه، و اون Compact کردن حافظه هستش. اما همه این مراحل به نظرتون باندازه کافی سریع هستش؟ فکرش رو کنید، وقتی GC شروع به آزاد سازی حافظه کنه، باید بره لیست همه root هایی که مساوی null هستن رو بیاره و دونه به دونه اقدام به آزادسازی اونا کنه که کاملا مشخصه که بسیار روند کندی هستش.

پس اومدن گفتن جای اینکه تو یه فاز بریم و object هایی که root اونها الان null هست رو آزاد کنیم، بیاییم این کار رو تو چند مرحله انجام بدیم. اما با توجه به این مفروضات که اولا Compact کردن بخشی از heap سریعتر از compact کردن کل heap هستش و ثانیا Object های قدیمیتر احتمالا بیشتر باقی می مونن تا object های جدیدتر.

اما فازها: گفتن ما 3 تا فاز تعریف میکنیم. فاز اول وقتی هستش که یه object ای new میشه. همه object هایی که new میشن تو فاز 1 ذخیره میشن تا وقتی که فاز یک پر بشه. هر وقت این اتفاق افتادش، GC شروع به آزادسازی تمام Object هایی میکنه که دیگه جایی reference نشدن و مابقی که هنوز در حال استفاده هستن compact میشن و به سمت چپ میان. (حافظه رو خونه های چپ به راست افقی در نظر بگیرید). تمام Object هایی که باقی می مونن (یعنی هنوز در حال استفاده هستن)، میرن تو فاز 2. وقتیکه دوباره فاز 1 پر شدش، همه Object هایی که تو فاز 2 بودن میرن تو فاز 3. هر وقت هر 3 فاز پر بشن، OutOfMemoryException، داده میشه.

////////////////////
بحث مدیریت حافظه ،یکی از بحث های مهم در عرصه نرم افزار نویسی است.مثلا شرکت موزیلا بعد از FIreFox2 یک فراخوان برای برنامه نویس ها میده تا مشکل استفاده از حافظه زیاد موقع بارگذاری رو کم کنه (و انصافا توی FireFox3 موفق هم بوده).این مطلب اونقدر در مورد این نرم افزار مهم بوده که طرفدارای دو آتشه IE این مشکل رو به عنوان اولین ایراد FireFox مطرح می کردند!
با این موضوع که در نرم افزار های داخلی هم این مطلب رعایت نمیشه (البته معمولا) موافقم.شاید به این دلیل که برنامه نویسی که داره برنامه رو می نویسی ،خودش روی یک سیستم آنچنانی (از نرم افزار تا سخت افزار) کار می کنه و نمی دونه بعضی ها می خواهند این نرم افزار رو روی یک سیستم با حداقل امکانات اجرا کنند! به همین دلیل سینه چاک های VB زیادن و VC کارها کم! در هر حال بگذریم....

اما دوستان به منابع مدیریت شده (که مقصود مدیریت توسط GC) و منابع مدیریت نشده (که همچنان کمافی السابق باید توسط برنامه نویس مدیریت بشه - مثل فایلها ، Connection ها و ...) اشاره کردند.ولی قبل از این که در مورد این دو سئوال که 'آیا این بیهوده نبوده که متغیرهای محلی dt وda را هم Dispose کرده' و 'آیا این کلاس مدیریت شده است' با هم بحث کنیم.می خواهم یک بار دیگر مراحل کار GC رو مرور کنیم (هر چند همه می دونیم ولی برای استناد به اون به تکرار نیازه!).درضمن این مطالب برای انواع دارای نوع Refence است نه انواع مقدار (مثل int یا struct و ..).

1) وقتی از کلمه کنیدی new استفاده می کنید،CLR یک فضا برای اون تخصیص داده و به GC اعلام می کنه کی متغیر جدید داره و یک شمارنده Refrence برای اون ایجاد میکنه!
2) اگر این فضای تخصیص داده شده به متغیری تخصیص داده بشه (مثلا متغیری که به این فضا اشاره دارد به متغیر دیگری تخصیص داده بشه)،به شکل اتوماتیک به شمارنده Refrence افزوده می شه.به همین دلیل است که دات نت اجازه پیاده سازی عملگر انتساب (=) رو به برنامه نویسی نمی ده (در یک کلاس همه عملگر ها به غیر از انتساب قابل پیاده سازی هستند!).
3) هنگامی که یک متغیر نابود می شه (مثلا یک متغیر محلی که با خروج از تابع نابود میشه)،یا متغیری که قبلا یک Refrence به محلی از حافظه داشته،حالا اشاره به محل دیگه ای کنه (با انتساب مقدار دیگه ای به اون)،به شکل اتوماتیک از شمارنده Refrence کاسته می شه.توجه کنید که به محض به صفر رسیدن این شمارنده ،فضای مورد بجث آزاد نمیشه!
4) هر گاه GC اقدام به پاک سازی حافظه کنه (اینجاست که زمان این کار معلوم نیست) ،دو عملیات رخ میده اول اینکه فضا هایی که شمارنده Refrence اونها صفر است ،رها سازی می شوند.دوم تمام اطلاعات موجود در حافظه دوباره باز آرایی میشوند تا فضا های مورد استفاده در کنار هم قرار گیرند (بحث Boxing هم مربوط به این کار GC است).البته برنامه نویس با برنامه متوجه جابجایی اطلاعات در حافظه نمی شوند و عملا با همان Refrence که در متغیر ها هستند دسترسی به مقادیر ممکن است!(باید بگم این بحث کاملتر از این توضیح کوچک است ولی تا همینجا کافی به نظر می رسه).

حالا سئوال اینه که 'کی GC اقدام به رها سازی حافظه می کنه (مرحله4)؟'.زمان این عمل معلوم نیست (درضمن این عملیات دریک Thread مستقل از Thread اصلی انجام میشه).شاید چند ثانیه نیز طول بکشه! پس نتیجه میگیریم که بین مراحل سوم و چهارم ،با اینکه دیگر نیاز به حافظه مورد نحث نیست،ولی هنوز در حافظه مکانی برای آن منظور شده.شاید برای متغیر های کوچک ،خیلی مهم نباشد ولی برای متغیر هایی با حافظه مصرفی بزرگ،قطعا می تواند نگران کننده باشد.
برای این کار دو راه حل را میشناسم (راه های دیگری هم شاید باشد که من از آن بی خبر باشم).راه اول استفاده از متد Dispose اشیای است.در این حالت فضای مصرفی آنها تا حد امکان کوچک میشود.مثلا کد زیر را در یکی از صفحات همین سایت دیدم :

public void PaintGradient(Control _control, LinearGradientMode _direction, Color _gradientColorStart, Color _gradientColorEnd)
{
//http://barnamenevis.org/showpost.php?p=468043&postcount=35
LinearGradientBrush gradBrush;
gradBrush = new LinearGradientBrush(new Rectangle(0, 0, _control.Width, _control.Height), _gradientColorStart, _gradientColorEnd, _direction);
Bitmap bmp = new Bitmap(_control.Width, _control.Height);
Graphics g = Graphics.FromImage(bmp);
g.FillRectangle(gradBrush, new Rectangle(0, 0, _control.Width, _control.Height));
_control.BackgroundImage = bmp;
_control.BackgroundImageLayout = ImageLayout.Stretch;
}



به نظر شما فراخوانی یک متد مثل Paint برای یک کنترل چند بار صورت می گیرد.شاید چندین بار در یک ثانیه.البته در یک پیاده سازی بد می توان این مقدار را به چند هزار بار در یک ثانیه رساند (توجه کنید که فراخوانی یک متد مثل این، کاملا به پیاده سازی وابسته است).همینطور که ملاحظه می کنید شی gradBrush هرگز نابود نمی شود تا وقتی که GC به حسابش برسد! حالا اگر فرض کنید که در یک برنامه این متد چند ده بار در ثانیه فراخوانی شود و GC نیز 5 ثانیه یک بار عمل کند (تکرار می کنم : این که GC کی عمل کند به خیلی عوامل وابسته بوده و قابل پیش بینی نیست!).در نتیجه تا زمانی که GC عمل کند ،چه مقدار حافظه،بی مورد مصرف شده است!در حالی که در انتهای این متد می توان با یک خط ساده مثل
gradBrush.Dispose();
این حافظه را به سیستم برگرداند!در این متد فقط یک متغیر اینگونه است.ممکن است در برنامه های بزرگتر،تعداد متغیر ها هم بیشتر و هم فضای مورد استفاده آنها بزرگتر و تعداد فراخوانی آنها بیشتر باشد! پس بهتر است در کلاس هایمان اولا عادت کنیم که متد Dispose را پیاده سازی کنیم و ثانیا در انتهای هر متد متغیر های بلا مصرف را Dispose کنیم (کار از محکم کاری عیب نمی کند!).توجه کنید که مخرب کلاس (destructor) در فرایند مرحله 4 GC فراخوانی می شود.
راه حل دوم هم استفاده از متدهای خوده GC است مثل Collect و WaitForPendingFinalizers (این متد Thread اصلی را می خواباند تا فرآیند پاک سازی حافظه انجام شود.به عبارت دیگر یک جور فعال سازی GC برای مرحله 4 است)،که معمولا به شکل
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();



و چند تا دیگه

veniz2008
پنج شنبه 07 شهریور 1392, 04:04 صبح
ولی چرا اون Dispose ها عمل نمیکنن اگر هم عمل میکنن پس اون پیام دلیلش چیه؟
چطور میشه حافظه اختصاص یافته به یک شی رو دید که چقدر است
ممنون
سلام دوست من.
این کد رو بذار توی فرم لود :

Timer memoryWatcher = new Timer();
memoryWatcher.Interval = 500;
memoryWatcher.Tick += (o, a) =>
{
Process app = Process.GetCurrentProcess();
this.Text = "Total Memory used : " + ((app.PrivateMemorySize / 1024) / 1024).ToString("0 MB");
this.Text += "\t And Total Time Elapsed : " + app.TotalProcessorTime.ToString();
};

memoryWatcher.Start();
یه گرید بذار روی فرم و این کد رو هم داخل یه دکمه بذار :

DataTable dt = new DataTable();
dt.Columns.Add("Row");
dt.Columns.Add("FirstName");
dt.Columns.Add("LastName");
dt.Columns.Add("Address");
//
for (int i = 1; i <= 750000; i++)
{
dt.Rows.Add(i, "AliReza", "Akbari", "Iran - Tehran");
}
datagridview1.DataSource = dt;
حالا دیتاتیبل رو dispose کن.نتیجه جالبی رخ میده.
خواهی دید که هیچ اتفاقی نمی افته و حافظه تخصیص داده شده به دیتاتیبل باز پس گرفته نمیشه. (میتونی از task manager هم رم سیستمت رو زیر نظر بگیری).

esafb52
پنج شنبه 07 شهریور 1392, 04:10 صبح
اتفاقا جناب veniz2008 (http://barnamenevis.org/member.php?155296-veniz2008)
من این کد رو تست کردم و نتیجه هم داد شما هم تست کنید
try
{
dt = new DataTable();
dataAdapter = new SqlDataAdapter("loaddall", connection);
dataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
dataAdapter.Fill(dt);
dataGridView1.DataSource = dt;
long m = GC.GetTotalMemory(true);
MessageBox.Show(m.ToString());
}
finally
{
dt.Dispose();
dataAdapter.Dispose();
long m = GC.GetTotalMemory(true);
MessageBox.Show(m.ToString());

}

veniz2008
پنج شنبه 07 شهریور 1392, 04:42 صبح
دوست من، متد GetTotalMemory میزان حافظه مصرفی رو برحسب بایت نمایش میده. ولی همونطور که گفتم کدهایی که نوشتید رم مصرفی رو به سیستم بر نمیگردونه. با dispoce نمیتونید تضمین کنید که حافظه مصرفی برگشت داده بشه.

esafb52
پنج شنبه 07 شهریور 1392, 04:44 صبح
دوست من، متد GetTotalMemory میزان حافظه مصرفی رو برحسب بایت نمایش میده. ولی همونطور که گفتم کدهایی که نوشتید رم مصرفی رو به سیستم بر نمیگردونه. با dispoce نمیتونید حافظه رو پس بگیرید.
پس چه جوری میشه این کار رو کرد؟ممنون میشم راهنمایی کنید یا لینکی چیزی بهم بدین
ممنون

FastCode
پنج شنبه 07 شهریور 1392, 06:11 صبح
خیلیش درست بود و برای خیلیش هم دوست دارم مترجم رو از نزدیک ببینم.
خیلی چیزها رو هم کلا نگفته بود.مثلا اصلا به LOH و سناریو های چند هسته ای اشاره نکرده.به روش علامتگزاری اشاره نکرده.به object های static اشاره نشده.روش تخصیص pointer ها رو توضیح نداده.اندازه heap ها رو توضیح نداده.object pinning رو هم خورده. و اصلا اسمی از Finalizer Queue نیاورده.در ضمن طوری مطلب رو بیان کرده که کاربر فکر میکنه هر لحظه در اجرای کدش ممکنه دستوری اجرا کنه که باعث بشه یک شیئ از حافظه حذف بشه.و این گوشه ای از ایراداتش بود.
با این حال باز هم واقعا برای من جای تعجب است که شما اینها رو میدونی و باز هم از GC.Collect استفاده میکنی.البته ممکنه همه اش هم تقصیر شما نباشه.برای اینکه یک مقدار بهتر با موضوع آشنا بشید به نظر من بهتره یک مقدار راجع به Page Table ها ,TLB و اینکه سیستم عامل واقعا چه زمانی فکر میکنه یک نقطه از حافظه خالی هست تحقیق کنید.