دوشنبه، اردیبهشت ۰۳، ۱۳۸۶

آزادسازی منابع در C#

حافظه در .NET مدیریت شده است. مدیریت شده بدین معنی که اگر شیءای در حافظه باشد و به آن اشاره‌ای نشود، این شیء از دید Framework یک آشغال است و جمع‌آوری (پاک) می‌شود. به این عمل Garbage Collection می‌گویند. اما بسیاری از منابع مدیریت شده نیستند. مثلا برنامه‌نویس باید فایلی را که باز کرده، بعد از استفاده، خود، ببندد. همین طور اتصال به database باید توسط خود برنامه‌نویس قطع شود و ... می‌توان آزادسازی این منابع را در مخرب کلاس انجام داد. در C# مخرب‌ها درست مانند C++ تعریف می‌شوند.

C#
class SomeClass
{
    ~SomeClass()
    {
        // release resources here
    }
    ...
}

این تابع پس از کامپایل شدن تبدیل به تابعی virtual با نام Finalize() می‌شود. در واقع کد بالا پس از کامپایل شدن فرمی پیدا می‌کند که انگار کد زیر نوشته شده:

C#
class SomeClass
{
    virtual void Finalize()
    {
        try
        {
            // release resources here
        }
        finally
        {
            base.Finalize();
            // chaining to the base class
        }
    }
    ...
}

تابع Finalize() یا مخرب را فقط Garbage Collector (GC) می‌تواند صدا بزند نه برنامه‌نویس. همچنین برنامه‌نویس نمی‌تواند تابع Finalize() را خودش تعریف کند. هر گونه شباهت توابع برنامه‌نویس به تابع Finalize() بسته به میزان شباهت موجب ایجاد warning یا error می‌شود. تنها و تنها تابع مخرب به فرم C++ مجاز است.

 

اگر GC شیءای را پاک کند، مخربش را هم صدا می‌زند. اما معلوم نیست چه زمانی Garbage Collector به سراغ شیء می‌رود. اگر به شیء تنها یک reference اشاره کند با مساوی null قرار دادن آن reference، شیء برای جمع‌آوری شدن علامت می‌خورد و به صف آشغال‌ها اضافه می‌شود. حال اگر Garbage Collector را مجبور کنیم تمام آشغال‌ها را پاک کند، شیء ما میان مابقی آشغال‌ها پاک می‌شود. این کار زمان‌بر است چون که به بهانه پاک کردن یک شیء کل آشغال‌ها پاک خواهند شد.

C#
SomeClass someObject = new SomeClass();
// do something with it
...
// when finished set
someObject = null;
// force GC to collect all garbage
GC.Collect();

راه دیگر استفاده از IDisposable است. اگر کلاسی IDisposable را پیاده‌ (implement) کند، می‌تواند از متد Dispose() برای آزاد کردن منابع استفاده کند. این تابع را برنامه‌نویس می‌تواند صریحا صدا بزند. همان گونه که از اسم آن پیداست کلاسی که IDisposable را پیاده کند، disposable یا قابل دورانداختن می‌شود.

C#
public interface IDisposable
{
    void Dispose();
}

در پیاده‌سازی تابع Dispose() باید GC.SuppressFinalize() هم صدا زده شود تا دیگر GC شیء مذکور را برای بار دوم پاک نکند. برای پیاده کردن همچین سناریویی از یک متغیر Boolean که این‌جا اسمش را گذاشتیم disposed و دو نسخه از تابع Dispose() که یکی از آن دو متغیری Boolean به عنوان پارامتر می‌گیرد، به شکل زیر استفاده می‌شود.

C#
public class SomeClass
{
    public SomeClass()
    {
        // initialize resources
        disposed = false;
    }

    ~SomeClass()
    {
        dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if(!disposed)
        {
            if(disposing)
            {
                // dispose managed resources
                // e.g. call their Dispose()
            }
            // now dispose unmanaged resources
            // e.g. close files or database connections
            disposed = true;
        }
    }
    ...
    private bool disposed;
}

متغیر disposed موجب می‌شود که قطعه کد آزادسازی تنها یک بار اجرا شود. اگر این کار توسط فراخوانی تابع Dispose() انجام شود کلیه منابع چه مدیریت شده چه مدیریت نشده آزاد می‌شوند، چرا که دیگر قرار نیست GC به سراغ آن‌ها برود. اما اگر مخرب این کار را انجام دهد فقط منابع مدیریت نشده آزاد می‌شوند، چرا که منابع مدیریت شده قبلا توسط خود GC آزاد شده‌اند.

 

یک راه کوتاه و تمیز برای استفاده و بلافاصله آزاد کردن اشیایی که disposable‌ هستند استفاده از using است.

C#
using (someObject = new SomeClass())
{
    // use someObject
}

کد بالا پس از کامپایل به فرمی تبدیل می‌شود که انگار به شکل زیر نوشته شده بود.

C#
someObject = new SomeClass();
try
{
    // use someObject
}
finally
{
    if(someObject != null)
        someObject.Dispose();
}

مثلا در زیر روش استفاده از using برای باز کردن یک فایل و خواندن از آن نشان داده شده است.

C#
using (StreamReader sr = new StreamReader("file.txt"))
{
    // read the file
}

1 نظر:

ناشناس :

با سلام

توضیحاتتون خیلی کمک کرد. یک pictureBox داشتم که بعد از dispose کردن عکسش، عکس رو که یک فایل بود رها نمیکرد. وقتی GC.collect(); رو اضافه کردم، درست شد.

ممنون

ارسال یک نظر

جهت نمایش صحیح آدرس سایتتان، حتما قبل از آدرس //:http را درج کنید.