تبلیغات

نسل باران

|

بانک شماره و موبایل popup window

قالب های حرفه ای وردپرس

|

پلاگین وردپرس popup window

وب سایت دانشجویان کارشناسی و ارشد کامپیوتر خرم آباد - C# 5.0 و برنامه نویسی غیرهمزمان! - قسمت دوم
نسل باران
وب سایت دانشجویان کارشناسی و ارشد کامپیوتر خرم آباد
امام علی (ع) می فرمایند: « زکات العلم نشرهُ» زکات علم نشر آن است.
به سایت ما خوش آمدید
در مقاله دوم از سری مقالات سی شارپ 5.0، برنامه نویسی غیرهمزمان معرفی شد. برنامه نویسی غیرهمزمان این امكان را فراهم می كند كه فراخوانی توابع مورد نظر (كه با كلمه كلید async مشخص می شوند)، به صورت غیرهمزمان انجام شود. به این معنا كه اجرای برنامه منتظر اتمام فراخوانی تابع نمانده و ادامه پیدا می كند. در نتیجه می توان در فاصله زمانی بین فراخوانی تابع غیرهمزمان و اتمام آن، پردازش های دیگری انجام داد.

در این مقاله قصد بر آن است تا ساختار برنامه نویسی غیرهمزمان مورد بحث و بررسی قرار گیرد. اما پیش از آن تاریخچه ای از برنامه نویسی غیرهمزمان ارائه می شود.


تاریخچه

برنامه نویسی غیرهمزمان به عنوان بخشی از نسخه آینده سی شارپ یعنی 5.0 معرفی شده است و همانطور كه در مقاله قبلی هم اشاره شد، این ویژگی قبلا توسط زبان های رسمی دات نت از جمله سی شارپ و ویژوال بیسیك پشتیبانی نمی شده است. به جای آن ساختارهایی در دات نت فریم ورك وجود داشتند كه پیاده سازی الگوهای برنامه نویسی غیرهمزمان را ممكن می ساختند. اما نگاهی به تاریخچه دات نت نشان می دهد كه برنامه نویسی غیرهمزمان مبحثی جدید نبوده بلكه گرد و غبار تاریخ بر روی آن نشسته است!


Cω (سی اُمِگا)، نواده ای از سلسله C كه هرگز به قدرت نرسید!

شركت مایكروسافت، واحدی به نام Microsoft Research دارد كه تحقیق و توسعه در زمینه های پایه ای و كاربردی علوم كامپیوتر و مهندسی نرم افزار را عهده دار است. یكی از زمینه هایی كه Microsoft Research در آن ایفای نقش می كند، تولید و توسعه ی زبان های جدید برنامه نویسی ست. نمونه ای موفق در این عرصه، زبان جدید برنامه نویسی #F است كه در قالب ویژوال استودیو 2010 با عنوان ویژوال #F به صورت رسمی ارائه شد. #F یكی از پروژه های تحقیقاتی Microsoft Research بوده كه توسعه آن سال ها طول كشیده است.

#C زبان شی گرایی است كه همزمان با توسعه پلتفرم دات نت به عنوان اولین زبان دات نت طراحی شد (باقی زبان ها، نسخه های قبلی خود را با پلتفرم دات نت سازگار ساخته و پسوند Net. گرفته اند). در طراحی آن سعی شده است از ساختار و استایل بزرگان دنیای برنامه نویسی یعنی C و ++C استفاده شود. حال این سوال مطرح می شود كه آیا سلسله زبان های C دنباله دار است؟ جواب را باید در Microsoft Research جستجو كرد. نام پرابهت Cω (سی اُمِگا) در بین اسامی زبان های توسعه یافته بر مبنای سی شارپ به چشم می خورد.

Cω در ابتدا با نام كد #X شناخته می شده است كه توسط تیم داده مایكروسافت در اس كیو ال سرور و با همكاری تحقیقات مایكروسافت در بریتانیا طراحی شد. بعد از تركیب شدن آن با یك زبان تحقیقاتی دیگر به نام #Polyphonic C به Cω تغییر نام یافت. هدف از ارائه Cω تسهیل كار با انباره های داده مانند فایل های XML و پایگاه داده ها بوده است كه امروزه این ویژگی ها در LINQ نمود عینی پیدا كرده است! نكته جالب این است كه #Polyphonic C امكان برنامه نویسی غیرهمزمان را به Cω افزود كه این ویژگی نیز قرار است در نسخه سی شارپ 5.0 ظهور كند!


نمونه فراخوانی در Cω برای انتخاب داده ها

در مستندات Cω آمده است: "در Cω توابع می توانند به دو صورت همزمان و غیرهمزمان فراخوانی شوند. هنگامی كه تابعی به صورت همزمان فراخوانی می شود، فراخواننده تا زمان بازگشت اجرا از تابع، بلوك می شود مانند روال نرمال فراخوانی ها در #C. اما زمانی كه تابعی به صورت غیرهمزمان فراخوانی می شود، اجرا بدون بلوك كردن فراخواننده و بدون دریافت نتیجه ادامه پیدا می كند. بنابراین از نقطه نظر فراخوانی كننده، تابع غیرهمزمان مانند یك تابع void با تضمین بازگشت بلافاصله بعد از فراخوانی می باشد." حتی مثال های بسیاری در حوزه برنامه نویسی غیرهمزمان ارائه شده است. به عنوان مثال به ساده ترین آن ها توجه كنید:

public class Buffer {
   public async Put(string s);
   public string Get() & Put(string s) { return s; }
}

كلمه كلیدی async با رنگی متمایز مشخص شده است.

در پایان این تاریخچه، مطالعه مصاحبه InfoWorld در سال 2005 با آندرس هلسبرگ بسیار جالب خواهد بود. در این مصاحبه چندین سوال در مورد Cω مطرح شده است.


بررسی یك مثال گرافیكی از برنامه نویسی غیرهمزمان - بازی BreakAway در محیط WPF

اگر پیش نمایش (CTP) برنامه نویسی غیرهمزمان را دانلود كرده باشید، مثال های متنوعی برای بررسی نحوه برنامه نویسی غیرهمزمان در اختیار خواهید داشت. یكی از این مثال ها، بازی معروف BreakAway بوده كه تصاویر این بازی در زیر نشان داده شده اند. در این بازی سعی بر آن است كه از طریق هدایت پیكان ها توسط پد (كه بار رنگ مشكی نمایش داده شده است) به سمت بالا و هدف قراردادن و از بین بردن بلوك های رنگی كوچك موسوم به brick، به پیروزی دست پیدا كرد. با از بین رفتن هر بلوك كوچك، تعداد پیكان ها افزایش پیدا خواهد كرد. باخت در بازی به شرطی اتفاق می افتاد كه كاربر نتواند هیچ پیكانی را به سمت بالا برگشت دهد.

        

پس از كلیك بر روی دكمه Play، بازی آغاز می شود و تا زمان از بین رفتن تمامی بلوك ها ادامه پیدا می كند. برای بررسی بیشتر، سورس كد مربوط به فرم اصلی در زیر نمایش داده شده است.

static class Paddl2Game2csAsync
{
   // المان های گرافیكی تشكیل دهنده فرم
     static Application app = new Application();
     static Border frame = new Border {BorderThickness=new Thickness(15.0), BorderBrush=new SolidColorBrush(Color.FromRgb(0,0,0)), Width=432.0, Height=507.0};
     static Window window = new Window {Title="Super Breakaway C# Async!", SizeToContent=SizeToContent.WidthAndHeight, Content=frame};
 
    [STAThread]
    static void Main()
    {
        // رخدادی كه با نمایش فرم اجرا خواهد شد
        app.Startup += MainAsync;
        app.Run(window);
    }
   // تابع رخداد نمایش فرم به صورت غیرهمزمان تعریف شده است
    static async void MainAsync(object sender, StartupEventArgs e)
    {
        // نمایش صفحه خوش آمدگویی
        var cb = GameLogic.MakeIntroScreen();  // مقدار برگردانده شده tuple (content, button)
        frame.Child = cb.Item1;
        // پس از كلیك كاربر اجرا از این قسمت ادامه پیدا می كند
        await ButtonClick(cb.Item2);
 
        while (true)
        {
            // مقداردهی اولیه بازی
            var dat = GameLogic.InitializeGameData();
            // نمایش صفحه بازی
            frame.Child = GameLogic.MakeGameScreen(dat);
            while (dat.remaining > 0 && dat.active > 0)
            {
                  // اجرای بازی در نتیجه حركت دادن پد
                await TaskEx.Delay(10);
                GameLogic.MovePaddle(dat);
                GameLogic.UpdateBalls(dat);
            }
 
            // نماش صفحه برد بازی
            var cs = GameLogic.MakeVictoryScreen(dat.remaining); // مقدار برگردانده شده (content,sound)
            frame.Child = cs.Item1;
            await TaskEx.Delay(3000);
            cs.Item2.Stop();
        }
    }
 
    // تابع كمكی برای كلیك دكمه كه به صورت غیرهمزمان فراخوانی می شود
    static Task ButtonClick(Button b)
    {
        var tcs = new TaskCompletionSource<object>();
        RoutedEventHandler handler = null;
        handler = (sender, e) => { b.Click -= handler; tcs.TrySetResult(null); };
        b.Click += handler;
        return tcs.Task;
    }
}

یادآوری
  • برای تعریف توابع به صورت غیرهمزمان از كلمه كلید async استفاده می شود.
  • كلمه كلیدی await باعث می شود كه اجرا از خط كد مارك شده با آن، به محل فراخوانی برگردانده شود. پس از اتمام فراخوانی مارك شده با await، اجرا مجددا به آن بازگردانده می شود. مثلا (await ButtonClick(cb.Item2 به این معناست كه تا قبل از كلیك دكمه Play اجرا از تابع MainAsync خارج شده و پس از كلیك، مجددا به آن بازگشته و از خط بعد ادامه پیدا كند.
در قطعه كد بالا:
  • كلمات كلیدی و آبجكت های مرتبط با بحث برنامه نویسی غیرهمزمان با رنگ قرمز مشخص شده اند.
  • توابع مربوط به رخدادها (Startup و Click) به صورت غیرهمزمان تعریف شده اند.
  • تمام منطق بازی را كلاسی به نام GameLogic پیاده سازی كرده است. از جمله وظایف این كلاس تغییر ظاهر فرم بازی بر مبنای حالت های مختلف (خوش آمدگویی - جریان بازی - برد یا باخت) می باشد. (نحوه عملكرد این كلاس مورد بررسی قرار نمی گیرد)
  • مرز اجرای غیرهمزمان با رنگ مشخص شده است. نكته بسیار جالب این است كه تصویر سمت چپ نمایش داده شده در بالا، حاصل اجرای برنامه تا مرز فراخوانی غیرهمزمان می باشد! پس از آن، اجرا تا زمان كلیك كاربر بر روی دكمه Play از تابع MainAnync خارج می شود. (نتیجه این امر، واسط كاربری كاملا روان بوده كه می تواند به تمامی عملیات كاربر از جمله حركت ماوس و یا كلیك بر روی دكمه Play پاسخ گو باشد) به محض كلیك كاربر، اجرا مجددا به تابع MainAsync بازگذشته و با حلقه while ادامه پیدا می كند.
با توجه به مطالب بیان شده، می توان نكات مهم زیر را استنتاج كرد:
  • توابع واسط كاربری (كه برای انواع رخدادها ثبت می شوند) می توانند غیرهمزمان باشند. نتیجه این امر واسط كاربری كاملا روان و پاسخ گو به درخواست های كاربر تا اتمام فراخوانی غیرهمزمان خواهد بود.
  • مقدار بازگشتی توابع غیرهمزمان، آبجكتی از نوع Task می باشد.
Task چیست؟

یكی از بحث های مهم مطرح شده در دات نت فریم ورك 4.0، كتابخانه پردازش های موازی (Task Parallel Library) می باشد. این كتابخانه كه بخشی از Base Class Library می باشد، امكان راه اندازی وظایف موازی را فراهم می كند. یكی از آبجكت های اصلی كتابخانه پردازش های موازی Task می باشد كه معرف زمان آینده است. به این معنا كه عملی در زمان حال اجرا شده و در زمان آینده نتیجه این اجرا برگردانده می شود. برخی از ویژگی های آبجكت وظیفه به شرح زیر می باشد:

  • امكان زمابندی وظیفه (Scheduling)
  • امكان برقراری ارتباط پدر-فرزندی بین وظیفه ها و مدیریت آنها از این طریق
  • امكان لغو وظیفه (Cancellation)
  • امكان انتظار برای وظیفه بدون داشتن یك هندل خارجی برای آن (Waiting)
  • اتصال وظیفه ها از طریق فراخوانی ContinueWith

Task را می توان جایگزینی برای كلاس Thread دانست كه تا قبل از دات نت فریم ورك 4.0 برای راه اندازی عملیات چند ریسمانی وجود داشته است. نمونه كد زیر مثال ساده ای ست از نحوه راه اندازی یك Task. همانطور كه مشاهده می شود عملیات محاسبه امتیازات یك كاربر توسط تابع ComputePdMemberScores انجام شده و پس از اتمام محاسبات ، امتیاز كاربر برگردانده می شود:

var compute = Task.Factory.StartNew(() =>
{
    return ComputePdMemberScores("Abolfazl Hosnoddinov");
});


ساختار برنامه نویسی غیرهمزمان

آنچه كه تاكنون از برنامه نویسی غیرهمزمان مطرح شد، ظاهر امر بود. دو كلمه كلیدی async و await به كدها اضافه شده و برنامه نویسی همزمان را به غیرهمزمان تبدیل می كنند. آیا معجزه ای رخ داده است؟ در پاسخ اندكی انحراف از بحث لازم می باشد. آیا تاكنون از خود پرسیده اید كه برنامه های ویندوزی یا به عبارتی فرم ها كه برنامه ها را تشكیل می دهند، چگونه به عملیات كاربران خود پاسخ می دهند؟

یكی از مفاهیم موجود در سیستم عامل ویندوز، پیام (Message) می باشد. پیداست كه یك پیام، فرستنده، گیرنده و محتوا دارد. سیستم عامل برای برقراری ارتباط با اجزای تشكیل دهنده خود از سیستم پیامدهی (Messaging) استفاده می كند. همانطور كه می دانید برنامه ها و سرویس ها در سیستم عامل ویندوز در قالب یك پروسس فعالیت می كنند. وقتی كه برنامه ای اجرا می شود، یك پروسس در حافظه ایجاد شده كه معرف این برنامه تا زمان اتمام اجرای آن خواهد بود. فرض كنید كه كاربر بر روی صفحه نمایش كلیك می كند. سیستم عامل این عمل (Action) كاربر را دریافت كرده آن را در یك صف قرار می دهد تا سر فرصت به آن رسیدگی كند. از طرفی برنامه ای كه در حال اجراست باید به گونه ای از عملیات كاربر مطلع شود. این اطلاع رسانی از طریق سركشی مداوم آن به سیستم عامل (Messaging Loop) انجام می شود. به این صورت كه در یك حلقه، برنامه از سیستم عامل می پرسد "چه خبر؟! آیا اتفاقی افتاده است كه برای من جالب باشد؟!" و سیستم عامل نیز با بررسی صف رخدادها به برنامه پاسخ می دهد "بله... یك كلیك اینجا اتفاق افتاده است!". برنامه با دریافت این پیام در صورت داشتن تابعی ثبت شده برای آن (تابع كلیك دكمه در این سناریو) می تواند آن را فراخوانی كند. ارتباط بین برنامه و سیستم عامل را می توان به شكل زیر در نظر گرفت:

while(GetMessage(&msg, NULL, 0, 0) > 0)
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

همانطور كه مشاهده می شود، با هر فراخوانی، یك پیام دریافت شده و تفسیر و پردازش آن (كه ممكن است منجر به فراخوانی تابعی شود) انجام می شود. باید گفت كه سناریویی كاملا مشابه در برنامه نویسی غیرهمزمان اتفاق می افتد!

اگر قسمتی از كد با برچسب await مشخص شود، كامپایلر یك Task با یك Action متناظر برای آن ایجاد می كند. هنگامی كه اجرا به این قسمت از كد می رسد، Task ایجاد شده از طریق یك پیام به ریسمان (Thread) ایجاد كننده آن خبر می دهد كه سر فرصت مناسب Task ایجاد شده را فرخوانی كند. در نتیجه ریسمان اقدام به ایجاد یك awaiter (در ادامه بیشتر توضیح داده خواهد شد) می كند و كانال ارتباطی برای رد و بدل كردن پیام ها بین این دو برقرار می شود. به همین دلیل است كه اجرا در این قسمت منتظر نتیجه نمانده و بلافاصله به محل فراخوانی تابع غیرهمزمان بازمی گردد. پس از آنكه نوبت اجرای Task قرار گرفته در صف فرا می رسد، Action مربوط به آن اجرا شده و با اتمام فراخوانی پیام مجددی برای ریسمان مبنی بر اتمام كار Task صادر می شود. و اجرا مجددا به محل await باز می گردد. ممكن است برچسب await منجر به ایجاد شدن چندین Task شود كه بعد از اتمام فراخوانی تمامی آنها اجرا مجددا به محل await بازمی گردد. برای روشن تر شدن نحوه عملكرد كد غیرهمزمان به تصویر زیر دقت كنید (برگرفته از اسلایدهای ارائه آندرس هلسبرگ در PDC 2010):





همانطور كه مشاهده می شود تابع غیرهمزمان DoWorkAsync دو بار تابع غیرهمزمان ProcessFeedAsync را فراخوانی می كند. با این فراخوانی ها، Taskهای متناظر ایجاد شده در صف قرار می گیرند. Taskهای مربوط به نواحی await نیز ایجاد شده در صف قرار داده می شوند. رنگ های نواحی مختلف با Taskهای منتاظر خود یكسان می باشند. پس از اتمام تمامی Taskها پیام Done نمایش داده می شود.


نگاهی عمیق تر

برای بررسی دقیق تر بار دیگر یكی از پروژه های CTP برنامه نویسی غیرهمزمان مورد بررسی قرار می گیرد. این پروژه C# 101) AsyncSamples) نام دارد كه حاوی توابع متنوع از برنامه نویسی غیرهمزمان در عرصه های مختلف دات نت می باشد. ساده ترین تابعی كه می توان ازین مجموعه مورد بررسی قرار داد، AsyncIntroSingle نام دارد كه دانلود آدرس وب سایت داده شده را به شكل زیر انجام می دهد.



نكته قابل توجه این است كه برای هر تابع غیرهمزمان یك نسخه مشابه با ساختارهای قبلی موجود در دات نت فریم ورك نیز ارائه شده است. تابع AsyncIntroSingleBefore با بهره گیری از delegate و فراخوانی آن پس از اتمام دانلود، الگوی غیرهمزمان را پیاده سازی كرده است:




با استفاده از نرم افزار معروف و محبوب Reflector می توان فایل های اسمبلی دات نت (exe و dll) را باز كرده و سورس كد آنها را مشاهده كرد. (فایل های اسمبلی دات نت شامل كد (Microsoft Intermediate Language (MSIL می باشند كه Reflector قابلیت تبدیل آن به زبان های دات نت از جلمه سی شارپ را دارد) اگر با استفاده از Reflector فایل اجرایی پروژه بالا را باز كرده، سراغ تابع AsyncIntroSingle برویم با كمال تعجب به جای خط كد تعریف شده در تصویر بالا (كه عینا در سورس كد پروژه نیز تكرار شده است)، با محتوای زیر روبرو خواهیم شد (دقت كنید كه این كد از زبان MSIL مجددا به زبان سی شارپ برگردانده شده است به این معنا كه كامپایلر سی شارپ سورس كدهای موجود را به شكل زیر به زبان IL تبدیل می كند):




تابع AsyncIntroSingle، یك نمونه (Instance) از كلاسی با همین نام تولید می كند. این كلاس توسط كامپایلر تولید می شود و تمام سناریوی غیرهمزمان را پیاده سازی می كند. در ادامه برای روشن تر شدن مطلب محتوای كلاس AsyncIntroSingle ارائه می شود. (قسمت های مهم با رنگ مشخص شده اند)

[CompilerGenerated]
private sealed class <AsyncIntroSingle>d__0
{
    // فیلدها
    private bool $__disposing;
    private bool $__doFinallyBodies;
    public VoidAsyncMethodBuilder $builder;
    private int <>1__state;
    public AsyncSamplesCS <>4__this;
    private string <1>t__$await1;
    // به عملكرد این  فیلد در ادامه كدها دقت كنید
    private TaskAwaiter<string> <a1>t__$await2;
    public Action MoveNextDelegate;
 
    // توابع
    [DebuggerHidden]
    public <AsyncIntroSingle>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }
 
    [DebuggerHidden]
    public void Dispose()
    {
        this.$__disposing = true;
        this.MoveNext();
        this.<>1__state = -1;
    }
 
    public void MoveNext()
    {
        try
        {
            this.$__doFinallyBodies = true;
            if (this.<>1__state != 1)
            {
                if (this.<>1__state == -1)
                {
                    return;
                }
                     // سناریوی برنامه نویسی غیرهمزان اینجا تكمیل می شود، جایی كه آبجكت وظیفه برای دانلود كردن آدرس داده شده ایجاد می شود
                this.<a1>t__$await2 = new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov")).GetAwaiter<string>();
                this.<>1__state = 1;
                this.$__doFinallyBodies = false;
                if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
                {
                    return;
                }
                this.$__doFinallyBodies = true;
            }
            this.<>1__state = 0;
            this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
            this.<>4__this.WriteLinePageTitle(this.<1>t__$await1);
            this.<>1__state = -1;
            this.$builder.SetCompleted();
        }
        catch (Exception)
        {
            this.<>1__state = -1;
            this.$builder.SetCompleted();
            throw;
        }
    }
}

و در نهایت برای تكمیل شدن سناریو، محتوای ساختار TaskAvator نیز ارائه می شود (كافیست به عناوین موجود دقت كنید):

[StructLayout(LayoutKind.Sequential)]
public struct TaskAwaiter<TResult>
{
    private readonly TaskAwaiter m_awaiter;
    internal TaskAwaiter(Task<TResult> task, bool flowContext)
    {
        this.m_awaiter = new TaskAwaiter(task, flowContext);
    }
 
    public bool BeginAwait(Action continuation)
    {
        return this.m_awaiter.BeginAwait(continuation);
    }
 
    public TResult EndAwait()
    {
        this.m_awaiter.EndAwait();
        return ((Task<TResult>) this.m_awaiter.Task).Result;
    }
 
    public unsafe TaskAwaiter<TResult> GetAwaiter()
    {
        return *(((TaskAwaiter<TResult>*) this));
    }
}

همانطور كه در تشریح عملكرد برنامه نویسی غیرهمزمان بیان شد، برای تمامی ساختارهای غیرهمزمان یك Action و Task متناظر ایجاد می شود. در مثال بالا، MoveNext همان Actionی است كه با ایجاد یك نمونه از TaskAvator، آبجكت وظیفه (Task) را وارد سناریو می كند. MoveNext عنوانی است كه طراحان برای تداعی شدن مفهوم دنباله دار بودن فراخوانی ها در نظر گرفته اند. به این معنا كه بعد از اتمام كار یك Action، بلافاصله عملیات Action بعدی در دستور كار قرار می گیرد.


منابع برای مطالعه بیشتر


كلام آخر

در این مقاله سعی بر آن بود كه تصویر روشن تر و جامع تری از برنامه نویسی غیرهمزمان در ذهن خوانندگان ایجاد شود. async و await برای اعمال كم ترین تغییرات در كدهای همزمان و تبدیل آنها به سناریوهای غیرهمزمان ارائه شده اند و كامپایلر است كه به جای برنامه نویسان تمامی تغییرات لازم را اعمال می كند. باید منتظر ماند و دید كه این مبحث تا نهایی شدن خود دچار چه تغییرات و دگرگونی هایی خواهد شد. از همراهان گرامی، همكاران عزیز و خوانندگان ارجمند وب سایت، خواهشمند است ما را از نقطه نظرات و پیشنهادات خود بهره مند سازند. همراهی و استقبال شما، مشوق گرمی برای ارائه مقالات بعدی خواهد بود.


درباره وبلاگ


امام صادق(ع) می فرمایند: «اِنُّ لِکلِّ شیءٍ زکاةً و زکاةُ العِلمِ اَنْ یُعَلِّمَهُ اَهلَهُ» برای هر چیزی زکاتی است،و زکات علم آن است که آن را به اشخاص شایسته بیاموزید. (منبع: تحف.ص 364)

امام علی (ع) می فرمایند: « زکات العلم نشرهُ» زکات علم نشر آن است.

مدیر وبلاگ : s1390
مطالب اخیر
نویسندگان
صفحات جانبی
آمار وبلاگ
  • کل بازدید :
  • بازدید امروز :
  • بازدید دیروز :
  • بازدید این ماه :
  • بازدید ماه قبل :
  • تعداد نویسندگان :
  • تعداد کل پست ها :
  • آخرین بازدید :
  • آخرین بروز رسانی :
فرم تماس
نام و نام خانوادگی
آدرس ایمیل
امکانات دیگر
دریافت كد ختم صلوات
کلیه حقوق این وبلاگ برای وب سایت دانشجویان کارشناسی و ارشد کامپیوتر خرم آباد محفوظ است