گفتگو هایی در باب سی شارپ

the_king

مدیرکل انجمن
خیلی ممنون استاد علی
توی vss ، تابع BackupComplete که اصلا ورودی و خروجی ای نداره (نمیدونم با این حال به درد کجا میخوره) . تابع BeginBackupComplete را اگه سر آخر (بعد از متد DoSnapshotSet) فراخونی کنیم ، انگار اگه برای عملیات بکاپ مشکلی پیش نیاد و توابع های فراخونی شده ، Exception ای را ندن ، تابع BeginBackupComplete درست فراخونی میشه وگرنه اگه اون متدهای مربوط به عملیات بکاپ ، Exception را پرتاب کنن ، متد BeginBackupComplete هم موقع فراخونی ، خطای VssBadStateException رو میده . پس آیا از این حالت خطا دادن یا ندادن متد BeginBackupComplete میشه متوجه شد که بکاپ ، موفقیت آمیز بود یا نه؟

ورودی و خروجی BackupComplete رو برای چه منظوری لازم دارید؟ BackupComplete برای اینه که برنامه شما بتونه اعلام کنه که عملیات Backup با موفقیت خاتمه یافته، همین. اگر خطایی رخ بده شما با Exception ها با خبر شده اید. وقتی شما عملیاتی انجام دادید که با خطا و Exception مشخص کرده که درست انجام نشده، چرا می خواهید رخداد BackupComplete رو ایجاد کنید؟ میخواهید VSS رو فریب بدید یا فکر می کنید اون Exception رو باید نادیده بگیرید؟ لزومی نداره که وقتی متوجه خطا شدید، متد دیگری رو اجرا کنید که میدونید با اجراش خطای جدیدی ایجاد میشه.

اون سئوال قبلی یعنی سئوال "پس کلاس Parallel و کلاس Task ها چیزی نیستن جز اینکه نخ ها و thread های جدید ایجاد میکنن؟" جوابش چیه؟ درسته؟
خیر درست نیست. همچین عبارتی چطور میتونه درست باشه؟ اگر کلاس Parallel و کلاس Task ها چیزی نباشند جز اینکه نخ ها و thread های جدید ایجاد بکنند پس اولا چه فرقی بین Parallel و Task و Thread قائل شدید، ثانیا کل ساختارشون رو با چه معیاری مشابه فرض کردید؟

و اینکه درباره ی async و aware ، یه کم توضیح میدین؟
در حالت عادی وقتی روتینی رو فراخوانی می کنیم از ابتدا تا انتها توسط نخ فراخوان اجرا میشه، فرضا اگر داخلش فایلی رو کپی کنیم تا اتمام کپی دستورات بعدی در نخ اجرا نمیشه.
و اگر داخل روتینی، عملیاتی رو با نخ جدیدی اجرا کنیم، توقف ای در اجرای دستورات بعدی نخ فراخوان پیش نمیاد و ممکنه هر کدوم از نخ های فراخوان و جدید کارشون زودتر از دیگری خاتمه پیدا کنه، فرضا اگر در نخ جدید فایلی رو کپی کنیم، معلوم نیست که نخ اصلی زودتر به اتمام دستورات روتین اش میرسه یا کپی کردن فایل در نخ جدید.
اما معمولا در برنامه نویسی شرایطی پیش میاد که نخ فراخوان باید عملیاتی رو حتما بعد از اتمام اجرای نخ جدید انجام بده، فرضا نخ فراخوان متنی رو حتما بعد از کپی شدن فایل توسط نخ جدید در فرم نشون بده.
در اینجور مواقع await میتونه نخ فراخوان رو به سادگی برای اتمام اجرای نخ جدید منتظر نگهداره.
async مشخص میکنه که این روتین قراره یکسری عملیاتی رو بصورت ناهمگام انجام بده، یعنی داخلش عملا بجز نخ فراخوان یک نخ دیگه هم بکار خواهد رفت. دستورات داخل روتین در حالت عادی و با نخ فراخوان اجرا خواهند شد، مگر در جایی که با await مشخص بشه، یعنی اگر await ای در کار نباشه، خود async به تنهایی کاری انجام نمیده. وجود async یک راهنما است برای اینکه مشخص بشه حتما await یا await هایی در این روتین وجود داره.
await مشخص میکنه که فراخوانی فلان عملیات باید مجزا از نخ فراخوان انجام بشه و نخ فراخوان تا پایان عملیاتش منتظر خواهد موند و دستور بعدی رو اجرا نمی کنه مگر اینکه کار نخ جدید تموم بشه.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ورودی و خروجی BackupComplete رو برای چه منظوری لازم دارید؟ BackupComplete برای اینه که برنامه شما بتونه اعلام کنه که عملیات Backup با موفقیت خاتمه یافته، همین. اگر خطایی رخ بده شما با Exception ها با خبر شده اید. وقتی شما عملیاتی انجام دادید که با خطا و Exception مشخص کرده که درست انجام نشده، چرا می خواهید رخداد BackupComplete رو ایجاد کنید؟ میخواهید VSS رو فریب بدید یا فکر می کنید اون Exception رو باید نادیده بگیرید؟ لزومی نداره که وقتی متوجه خطا شدید، متد دیگری رو اجرا کنید که میدونید با اجراش خطای جدیدی ایجاد میشه.

ورودی و خروجی BackupComplete رو برای اینکه این تابع وقتی فراخونی شد ، مثلا یه ورودی دلیگیت بگیره تا تابع مون را فراخونی کنه (مثل تابع BeginBackupComplete).
بنابراین اشکالی نداره بجای متد BackupComplete ، از متد BeginBackupComplete استفاده کنم دیگه؟ درسته؟


خیر درست نیست. همچین عبارتی چطور میتونه درست باشه؟ اگر کلاس Parallel و کلاس Task ها چیزی نباشند جز اینکه نخ ها و thread های جدید ایجاد بکنند پس اولا چه فرقی بین Parallel و Task و Thread قائل شدید، ثانیا کل ساختارشون رو با چه معیاری مشابه فرض کردید؟

نه . منظورم این بود که اساس هر 3 کلاس این هستن تا نخ جدیدی درست کنن . یعنی این نیستش که دستورات ، بصورت موازی همزمان در هسته های مختلف ، اجرا بشن .
حالا فرق شون این میتونه باشه که در کلاس Thread ، نخ رو خودمون میتونیم جداگانه کنترل کنیم . در Parallel ، حلقه هایی هستن که نخ های جدیدی برای هر بار اجرای حلقه ، ایجاد میکنن و در کلاس Task باز هم خودش اتوماتیک نخ های جدیدی ایجاد میکنه.

در حالت عادی وقتی روتینی رو فراخوانی می کنیم از ابتدا تا انتها توسط نخ فراخوان اجرا میشه، فرضا اگر داخلش فایلی رو کپی کنیم تا اتمام کپی دستورات بعدی در نخ اجرا نمیشه.
و اگر داخل روتینی، عملیاتی رو با نخ جدیدی اجرا کنیم، توقف ای در اجرای دستورات بعدی نخ فراخوان پیش نمیاد و ممکنه هر کدوم از نخ های فراخوان و جدید کارشون زودتر از دیگری خاتمه پیدا کنه، فرضا اگر در نخ جدید فایلی رو کپی کنیم، معلوم نیست که نخ اصلی زودتر به اتمام دستورات روتین اش میرسه یا کپی کردن فایل در نخ جدید.
اما معمولا در برنامه نویسی شرایطی پیش میاد که نخ فراخوان باید عملیاتی رو حتما بعد از اتمام اجرای نخ جدید انجام بده، فرضا نخ فراخوان متنی رو حتما بعد از کپی شدن فایل توسط نخ جدید در فرم نشون بده.
در اینجور مواقع await میتونه نخ فراخوان رو به سادگی برای اتمام اجرای نخ جدید منتظر نگهداره.
async مشخص میکنه که این روتین قراره یکسری عملیاتی رو بصورت ناهمگام انجام بده، یعنی داخلش عملا بجز نخ فراخوان یک نخ دیگه هم بکار خواهد رفت. دستورات داخل روتین در حالت عادی و با نخ فراخوان اجرا خواهند شد، مگر در جایی که با await مشخص بشه، یعنی اگر await ای در کار نباشه، خود async به تنهایی کاری انجام نمیده. وجود async یک راهنما است برای اینکه مشخص بشه حتما await یا await هایی در این روتین وجود داره.
await مشخص میکنه که فراخوانی فلان عملیات باید مجزا از نخ فراخوان انجام بشه و نخ فراخوان تا پایان عملیاتش منتظر خواهد موند و دستور بعدی رو اجرا نمی کنه مگر اینکه کار نخ جدید تموم بشه.

خیلی ممنونم.
خیلی قشنگ بود توضیح تون .
اما من فلسفه ی این موضوع را متوجه نشدم . خوب اگه قراره نخ جاری مون (مثلا نخ اصلی) ، منتظر بمونه تا نخ جدید کارش را انجام بده و بعد از تمام شدن کدهای نخ جدید ، نخ جاری مون ادامه ی کدهاش رو اجرا کنه ، خوب چرا اصلا نخ جدیدی ایجاد میکنن؟ خوب کدهای نخ جدید را توی نخ جاری هم بنویسن ، دقیقا همین کار را انجام میده دیگه.

پس الان هر جا کلمه ی async (روی پروتوتایپ تابع) باشه ، اون تابع ، در همون نخ جاری اجرا میشه و هر جا کلمه ی await باشه اولا در نخ جدیدی اجرا میشه و دوما ادامه ی کدهای نخ جاری (کدهای بعد از await) اجرا نمیشن مگر اینکه کدهای نخ جدید (نخی که await تولید کرده بود) تموم بشه و بعد ادامه ی کدهای نخ جاری (کدهای بعد از await) ، اجرا بشن. درسته؟
حالا صرف اینکه کلمه ی کلیدی await را بکار میبریم ، نخ جدید ایجاد میشه یا اینکه زمانی که تابع Task.Run را فراخونی میکنیم؟ و آیا حتما باید در تابعی که await را فراخونی میکنیم ، مقدار Task را برگردونیم؟ (حالا مثلا تابع Task.Run را فراخونی کنیم؟)
بعد اینکه کد زیر چرا ارور "Cannot implicitly convert type 'int' to 'System.Threading.Tasks.Task<int>" را میده و باید برای رفع خطاش ، چی کار کنم؟ :


کد:
        private void BtnAsyncAway_Click(object sender, EventArgs e)
        {
           
            this.AsyncAway_1();
            MessageBox.Show("BtnAsyncAway_Click \nmain thread before  =  " + Thread.CurrentThread.ManagedThreadId);
        }

        private async void AsyncAway_1()
        {
            MessageBox.Show("AsyncAway_1 main thread before  =  " + Thread.CurrentThread.ManagedThreadId);
            Task<int> taskWait = await this.AsyncAway_2();
            MessageBox.Show("AsyncAway_1 main thread after  =  " + Thread.CurrentThread.ManagedThreadId);
        }

        private Task<int> AsyncAway_2()
        {
            return Task.Run<int>(new Func<int>(this.AsyncAway_3));
        }

        private int AsyncAway_3()
        {
            return 7 * 7;
        }
 

the_king

مدیرکل انجمن
ورودی و خروجی BackupComplete رو برای اینکه این تابع وقتی فراخونی شد ، مثلا یه ورودی دلیگیت بگیره تا تابع مون را فراخونی کنه (مثل تابع BeginBackupComplete).
بنابراین اشکالی نداره بجای متد BackupComplete ، از متد BeginBackupComplete استفاده کنم دیگه؟ درسته؟
متد BackupComplete یا BeginBackupComplete رو شما فراخوانی می کنید نه شخص دیگری. رخداد پایان Backup چیزی نیست که از جای دیگه به شما اطلاع داده بشه. مثل اینه که من به شما بگم هر وقت گشنه شدم بهم خبر بده. رخداد گشنه شدن من که توسط شما ایجاد نمیشه. رخدادی که کلاس شما ایجاد می کنه رو که از جای دیگه بهتون اطلاع نمیدن، خودتون وقتی Backup رو بدون خطا تمام کردید می توانید با اجرای BackupComplete اطلاع بدید، نه اینکه منتظر بمونید که از جایی دست پنهانی پیدا بشه و BeginBackupComplete رو فراخوانی کنه.

اما من فلسفه ی این موضوع را متوجه نشدم . خوب اگه قراره نخ جاری مون (مثلا نخ اصلی) ، منتظر بمونه تا نخ جدید کارش را انجام بده و بعد از تمام شدن کدهای نخ جدید ، نخ جاری مون ادامه ی کدهاش رو اجرا کنه ، خوب چرا اصلا نخ جدیدی ایجاد میکنن؟ خوب کدهای نخ جدید را توی نخ جاری هم بنویسن ، دقیقا همین کار را انجام میده دیگه.
استدلال تون منطقیه، اما در نظر نگرفتید که انتظار نخ با توقف نخ تفاوت داره. در هنگامی که نخ منتظر مونده میتونه به رخداد های دیگه پاسخ بده. بهترین راه برای درک این تفاوت اجرا کردن کد ئه :
کد:
        private Label label1 = new Label() { Text = ".", Font = new Font("Arial", 40f), Location = new Point(10, 10), AutoSize = true };
        private Timer timer1 = new Timer() { Enabled = true, Interval = 250 };
        private Button button1 = new Button() { Text = "Start", Bounds = new Rectangle(150, 20, 100, 25) };
        private Button button2 = new Button() { Text = "Start", Bounds = new Rectangle(150, 60, 100, 25) };

        private void Form1_Load(object sender, EventArgs e)
        {
            label1.Parent = this;
            button1.Parent = this;
            button1.Click += button1_Click;
            button2.Parent = this;
            button2.Click += button2_Click;
            timer1.Tick += timer1_Tick;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int sum = 0;
            button1.Text = $"Wait...";
            button1.Enabled = false;
            Application.DoEvents();
            sum = CalcSum();
            button1.Text = $"Finish {sum}";
        }

        private async void button2_Click(object sender, EventArgs e)
        {
            int sum = 0;
            button2.Text = $"Wait...";
            button2.Enabled = false;
            Application.DoEvents();
            await System.Threading.Tasks.Task.Run(() =>
            {
                sum = CalcSum();
            });
            button2.Text = $"Finish {sum}";
        }

        private int CalcSum()
        {
            var sum = 0;
            for (var i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                sum += i;
            }
            return sum;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (label1.Text.Length < 5)
            {
                label1.Text += ".";
            }
            else
            {
                label1.Text = "";
            }
        }
به وجود async در button2_Click و عدم توقف اجرای رخداد Tick در timer1 با وجود await توجه کنید.

پس الان هر جا کلمه ی async (روی پروتوتایپ تابع) باشه ، اون تابع ، در همون نخ جاری اجرا میشه و هر جا کلمه ی await باشه اولا در نخ جدیدی اجرا میشه و دوما ادامه ی کدهای نخ جاری (کدهای بعد از await) اجرا نمیشن مگر اینکه کدهای نخ جدید (نخی که await تولید کرده بود) تموم بشه و بعد ادامه ی کدهای نخ جاری (کدهای بعد از await) ، اجرا بشن. درسته؟
بله.

حالا صرف اینکه کلمه ی کلیدی await را بکار میبریم ، نخ جدید ایجاد میشه یا اینکه زمانی که تابع Task.Run را فراخونی میکنیم؟ و آیا حتما باید در تابعی که await را فراخونی میکنیم ، مقدار Task را برگردونیم؟ (حالا مثلا تابع Task.Run را فراخونی کنیم؟)
ایجاد شدن نخ بخاطر خود Task ئه، await صرفا برای انتظار نخ فراخوان ئه. await از شما یک Task اجرا شده میخواد، حالا به هر روشی که تعریف و ایجادش کنید باید یک Task اجرا شده باشه.

بعد اینکه کد زیر چرا ارور "Cannot implicitly convert type 'int' to 'System.Threading.Tasks.Task<int>" را میده و باید برای رفع خطاش ، چی کار کنم؟ :
کد:
        private void BtnAsyncAway_Click(object sender, EventArgs e)
        {
          
            this.AsyncAway_1();
            MessageBox.Show("BtnAsyncAway_Click \nmain thread before  =  " + Thread.CurrentThread.ManagedThreadId);
        }

        private async void AsyncAway_1()
        {
            MessageBox.Show("AsyncAway_1 main thread before  =  " + Thread.CurrentThread.ManagedThreadId);
            Task<int> taskWait = await this.AsyncAway_2();
            MessageBox.Show("AsyncAway_1 main thread after  =  " + Thread.CurrentThread.ManagedThreadId);
        }

        private Task<int> AsyncAway_2()
        {
            return Task.Run<int>(new Func<int>(this.AsyncAway_3));
        }

        private int AsyncAway_3()
        {
            return 7 * 7;
        }
کد:
        private async void AsyncAway_1()
        {
            MessageBox.Show("AsyncAway_1 main thread before  =  " + Thread.CurrentThread.ManagedThreadId);
            var task = new Task<int>(this.AsyncAway_2);
            task.Start();
            int taskWait = await task;
            MessageBox.Show("AsyncAway_1 main thread after  =  " + Thread.CurrentThread.ManagedThreadId);
        }

        private int AsyncAway_2()
        {
            MessageBox.Show("AsyncAway_2 thread  =  " + Thread.CurrentThread.ManagedThreadId);
            return 7 * 7;
        }
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
متد BackupComplete یا BeginBackupComplete رو شما فراخوانی می کنید نه شخص دیگری. رخداد پایان Backup چیزی نیست که از جای دیگه به شما اطلاع داده بشه. مثل اینه که من به شما بگم هر وقت گشنه شدم بهم خبر بده. رخداد گشنه شدن من که توسط شما ایجاد نمیشه. رخدادی که کلاس شما ایجاد می کنه رو که از جای دیگه بهتون اطلاع نمیدن، خودتون وقتی Backup رو بدون خطا تمام کردید می توانید با اجرای BackupComplete اطلاع بدید، نه اینکه منتظر بمونید که از جایی دست پنهانی پیدا بشه و BeginBackupComplete رو فراخوانی کنه.
خیلی ممنون استاد علی.

استدلال تون منطقیه، اما در نظر نگرفتید که انتظار نخ با توقف نخ تفاوت داره. در هنگامی که نخ منتظر مونده میتونه به رخداد های دیگه پاسخ بده. بهترین راه برای درک این تفاوت اجرا کردن کد ئه :
کد:
        private Label label1 = new Label() { Text = ".", Font = new Font("Arial", 40f), Location = new Point(10, 10), AutoSize = true };
        private Timer timer1 = new Timer() { Enabled = true, Interval = 250 };
        private Button button1 = new Button() { Text = "Start", Bounds = new Rectangle(150, 20, 100, 25) };
        private Button button2 = new Button() { Text = "Start", Bounds = new Rectangle(150, 60, 100, 25) };

        private void Form1_Load(object sender, EventArgs e)
        {
            label1.Parent = this;
            button1.Parent = this;
            button1.Click += button1_Click;
            button2.Parent = this;
            button2.Click += button2_Click;
            timer1.Tick += timer1_Tick;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int sum = 0;
            button1.Text = $"Wait...";
            button1.Enabled = false;
            Application.DoEvents();
            sum = CalcSum();
            button1.Text = $"Finish {sum}";
        }

        private async void button2_Click(object sender, EventArgs e)
        {
            int sum = 0;
            button2.Text = $"Wait...";
            button2.Enabled = false;
            Application.DoEvents();
            await System.Threading.Tasks.Task.Run(() =>
            {
                sum = CalcSum();
            });
            button2.Text = $"Finish {sum}";
        }

        private int CalcSum()
        {
            var sum = 0;
            for (var i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                sum += i;
            }
            return sum;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (label1.Text.Length < 5)
            {
                label1.Text += ".";
            }
            else
            {
                label1.Text = "";
            }
        }
به وجود async در button2_Click و عدم توقف اجرای رخداد Tick در timer1 با وجود await توجه کنید.


بله.


ایجاد شدن نخ بخاطر خود Task ئه، await صرفا برای انتظار نخ فراخوان ئه. await از شما یک Task اجرا شده میخواد، حالا به هر روشی که تعریف و ایجادش کنید باید یک Task اجرا شده باشه.


کد:
        private async void AsyncAway_1()
        {
            MessageBox.Show("AsyncAway_1 main thread before  =  " + Thread.CurrentThread.ManagedThreadId);
            var task = new Task<int>(this.AsyncAway_2);
            task.Start();
            int taskWait = await task;
            MessageBox.Show("AsyncAway_1 main thread after  =  " + Thread.CurrentThread.ManagedThreadId);
        }

        private int AsyncAway_2()
        {
            MessageBox.Show("AsyncAway_2 thread  =  " + Thread.CurrentThread.ManagedThreadId);
            return 7 * 7;
        }

تشکری مجدد استاد علی :rose:
آو چه جالب !!!!!!! :shock:
بله . حتی توی کد دوم (کد من رو که اصلاح کردین) هم این چیز (پاسخگو بودن بقیه ی رخدادها) مشخص هست (زمانی که در نخ جدید ، پیام "AsyncAway_1 main thread = " را میده و نخ جاری مون هنوز اجرا نشده) . البته اینو یه جورایی برای خودم بگم که فقط با متد Start (در کلاس Task) این کار انجام میشه . یعنی متد RunSynchronously ، همونطور که از اسمش مشخص هه ، اون متد را داخل همون نخ اجرا میکنه .
اما واسه ی من خیلی جای تعجب و پرسش هه که چرا این جوری هه؟ چطور میشه که هنوز نخ مون (در اینجا ، نخ اصلی) هنوز کدهاش تموم نشده ، ولی در همین نخ ، رویدادهای دیگه ، جوابگو هستن؟!! آخه باید کد تموم بشه تا رویداد ، جواب گو باشه دیگه . این طور نیست مگه؟ اگه این طور نیست ، پس چرا در حالت عادی و همینطور اینکه خودمون نخ جدیدی با کلاس Thread میسازیم و مدیریت میکنیم ، این طوره و تا کدهای اون نخ تموم نشه ، رویدادهای اون نخ و همینطور اجرای بقیه ی کدها در بقیه ی رویدادها ، پاسخگو نیستن؟
اینجا یه چیزی بگم اونم اینکه این چه نوع نامگذاری هه که کردن . Synchronous یعنی همزمان دیگه . درسته؟ یعنی کدها باید همزمان اجرا بشن . زمانی کدها ، همزمان اجرا میشن که نخ جدیدی ایجاد بشه . پس در Synchronous باید نخ جدید ایجاد شه اما در Synchronous ، در همون نخ اجرا میشه پس کدها بصورت متوالی یا به عارتی غیر همزمان اجرا میشن . ASynchronous یعنی ناهمزمان . که باید برعکس قبلی باشه که نیست ! چرا این جوری نامگذاری کردن؟!!

بع متد Wait در کلاس Task چی کار میکنه؟ توی توضیحش نوشته که "منتظر میمونه تا شی Task ، اجراش را کامل کنه" . اما بصورت پیش فرض که همین کار را میکنه . یعنی کلمه ی await را وقتی بکار میبریم ، همین کار را میکنه دیگه . درسته؟ اگه درسته ، پس کاربرد متد Wait کجاست؟
 

the_king

مدیرکل انجمن
اما واسه ی من خیلی جای تعجب و پرسش هه که چرا این جوری هه؟ چطور میشه که هنوز نخ مون (در اینجا ، نخ اصلی) هنوز کدهاش تموم نشده ، ولی در همین نخ ، رویدادهای دیگه ، جوابگو هستن؟!! آخه باید کد تموم بشه تا رویداد ، جواب گو باشه دیگه . این طور نیست مگه؟ اگه این طور نیست ، پس چرا در حالت عادی و همینطور اینکه خودمون نخ جدیدی با کلاس Thread میسازیم و مدیریت میکنیم ، این طوره و تا کدهای اون نخ تموم نشه ، رویدادهای اون نخ و همینطور اجرای بقیه ی کدها در بقیه ی رویدادها ، پاسخگو نیستن؟

اینکه بخاطر تجربیات قبلی تون همچین دیدی به نخ ها پیدا کنید طبیعیه چون تا به حال همچین رفتاری ازشون دیده بودید و تازه الانه که به نظرتون رفتارشون متفاوت بوده، اما در نظر بگیرید که اولا همزمانی نخ ها در اغلب اوقات یک مفهوم ظاهری و شبیه سازی شده است، چون در سیستمی که بصورت فیزیکی فرضا نهایتا در مجموع 16 نخ میتوانست در کل هسته ها اجرا بشه، صدها پروسه و هزاران نخ در حال اجرا هستند، که مدام بین نخ ها سوئیچ میشه و در بیشتر موارد واقعا همزمانی لحظه ای در کار نیست و فقط به نظر میاد که همزمان اجرا میشن.
ثانیا نخ هایی که شما میسازید، یک شیء نرم افزاری و کاملا مجازی هستند، شما اون چند تا شاخه اجرایی در هسته رو مستقیم تحت کنترل ندارید و توقف نخ شما به این معنی نیست که یک بخش اجرایی از هسته دیگه بخواب بره، فوری میره سراغ یک عملیات دیگری که در صف انتظار برای اجرا شدن قرار داره.
ثالثا مدیریت Task ها مثل مدیریت حافظه Managed بهینه سازی شده و هوشمندانه است تا از منابع سیستم در حدی که توانش هست به بهترین شکل استفاده کنه. خیلی عادیه که در شرایطی که نیازی به نخ جدید نباشه از همون نخ های قبلی در دسترس برای اجرا شدن عملیاتی استفاده کنه. اگر شما n تا Task برای اجرا شدن دارید با توجه به ترتیب و نوبت اجرا ممکنه با n - m نخ کارشون رو راه بیاندازه.
رابعا ساختار فرم ها و اساس طراحی سیستم عاملی مثل ویندوز بر رخداد ها است، اگر رخدادی اتفاق بیافته، در صف قرار میگیره تا نوبت اجرا شدنش بشه. همونطور که وقتی شما در یک سیستم کند و به شدت مشغول سریع تایپ می کنید یا با ماوس جایی کلیک می کنید، رخداد های مربوط به تعامل شما اونطور در صف منتظر باقی میمونند که نخ فرصت پاسخگویی بهشون رو پیدا کنه. برای همینه که رخدادی مثل Timer.Tick میتونه در زمانی که روتین شما با await معطل شده اجرا بشه. شما با await نخ رو به خواب نمی برید، نخ رو برای اجرای روتین تون منتظر نگه میدارید که شرایط متفاوتی با بخواب رفتن نخ داره.

اینجا یه چیزی بگم اونم اینکه این چه نوع نامگذاری هه که کردن . Synchronous یعنی همزمان دیگه . درسته؟ یعنی کدها باید همزمان اجرا بشن . زمانی کدها ، همزمان اجرا میشن که نخ جدیدی ایجاد بشه . پس در Synchronous باید نخ جدید ایجاد شه اما در Synchronous ، در همون نخ اجرا میشه پس کدها بصورت متوالی یا به عارتی غیر همزمان اجرا میشن . ASynchronous یعنی ناهمزمان . که باید برعکس قبلی باشه که نیست ! چرا این جوری نامگذاری کردن؟!!
خیلی از کلمات در ترجمه معنی های متفاوتی دارند که همه معنی ها برای همه کاربرد ها مناسب نیست و از برخی شون برداشتی می کنید که با منظور اصلی متفاوته.
Synchronous رو همگام معنی کنید بهتره، نه همزمان. منظور از همگام اینکه مستقل از هم کار نمی کنند، با هم هماهنگ و همگام هستند. مثل دو تا دستور در روتین تون که پشت سر هم اجرا میشوند، مثل عملیات Cut و Paste کردن فایل که اول باید Cut انجام بشه و بعد Paste.
اما Asynchronous یعنی ناهمگام، یعنی بین شون هماهنگی در کار نیست، شروع و پایان شون لازم نیست با هم هماهنگ بشه. فرضا برنامه هایی که همزمان که در حال کار عادی برای کاربرشون هستند، نسخه جدید شون رو هم از اینترنت پیدا می کنند و دانلود می کنند، هیچ نیازی هم به هماهنگ کردن این دو کارشون نیست.

بع متد Wait در کلاس Task چی کار میکنه؟ توی توضیحش نوشته که "منتظر میمونه تا شی Task ، اجراش را کامل کنه" . اما بصورت پیش فرض که همین کار را میکنه . یعنی کلمه ی await را وقتی بکار میبریم ، همین کار را میکنه دیگه . درسته؟ اگه درسته ، پس کاربرد متد Wait کجاست؟
نه. فرق داره. await نخ شما رو بخواب نمی برد و می توانست به کار دیگری برسه، اما Task.Wait همونجا نخ رو بخواب میبره و دیگه نخ اجرا کننده نمیتونه هیچ فعالیت دیگری داشته باشه.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
خیلی ممنون استاد علی:rose:
پس کلا async await کردن ، با Sleep کردن نخ ، متفاوته و برعکسش ، باعث نمیشه نخ به خواب بره.
بعد اینکه همون کدی که در پست 1096 دادم (برای جستجو در فایل) ، اونو اگه در نخ جدید بنویسم (مثلا مثل همون متد SearchInDirectorySynchronize ، از Parallel.ForEach استفاده کنم) ، آیا در پردازنده های چند نخی تفاوتی در زودتر اجرا شدن تابع و کلا در کم شدن زمان ، خواهد داشت؟ من توی پردازنده ام که تست کردم (پردازنده ام چند نخی نیست) ، تفاوت خاصی نداشت . مثلا در 20 ثانیه ، شاید 2 ثانیه یا کمتر ، تفاوت زمانی داشت.
بعد اینکه کنترل progress bar در دات نت را چجوری میشه حالتی که نور میده و مدام از چپ به راست میره را کنسل کرد که دیگه نور نده؟ متوجه ی منظورم از نور ، میشید ؟
 

the_king

مدیرکل انجمن
بعد اینکه همون کدی که در پست 1096 دادم (برای جستجو در فایل) ، اونو اگه در نخ جدید بنویسم (مثلا مثل همون متد SearchInDirectorySynchronize ، از Parallel.ForEach استفاده کنم) ، آیا در پردازنده های چند نخی تفاوتی در زودتر اجرا شدن تابع و کلا در کم شدن زمان ، خواهد داشت؟

پردازنده چند نخی هارد دیسک شما رو به چند قسمت مستقل تقسیم نمی کنه، تعداد هد های هارد دیسک رو هم که باید موقع جستجو محتویات سکتور ها رو بخونند افزایش نمیده. معطلی جستجو هم ترکیبی است از معطلی برای پردازنده و حافظه RAM و هارد دیسک. زمانی هم که صرف پاسخگویی پردازنده و جستجوی حافظه RAM و ... میشه نسبت به زمانی که صرف جستجوی فیزیکی در هارد دیسک میشه فوق العاده کمه، اصلا قابل مقایسه نیستند. به همین جهت چیزی که شما رو در عمل معطل میکنه پردازنده نیست، هارد دیسک ئه که هر چند تا پردازنده هم باهاش درگیر باشه فرقی بحالش نمی کنه.
به ماهیت چیزی که دارید ازش اطلاعات میگیرید فکر کنید، فرض کنید فقط یک نفر پاسخگو برای تلفن 118 وجود داشته باشه، نه چند نفر. حالا شما قراره 200 تماس داشته باشید. چه با یک نفر و چه با دویست نفر با 118 تماس برقرار کنید، کمکی به کاهش زمان 200 تماس نمی کنه، چون اون پاسخگو تا به تماس یک نفر پاسخ نده، تماس های باقیمونده معطل میمونه. در مورد جستجو در یک هارد دیسک هم تا حدودی همینطوره و در اغلب موارد هم درگیر کردن هارد دیسک با چند عملیات بجای افزایش زمان کندترش می کنه چون هد ها باید مدام بین مکان های متفاوت جابجا بشن.

من توی پردازنده ام که تست کردم (پردازنده ام چند نخی نیست) ، تفاوت خاصی نداشت . مثلا در 20 ثانیه ، شاید 2 ثانیه یا کمتر ، تفاوت زمانی داشت.
ممکنه نهایتا همین نتیجه رو بگیرید اما در کل مقایسه ها رو باید در شرایط یکسان و برای زمان های طولانی اجرا کنید، مثلا اطلاعاتی که قبلا یکبار در حافظه cache شده طبعا در دفعه بعد سریعتر جستجو میشه، چون از cache میخونه، نه هارد دیسک. یا فرضا وقتی ویندوز تون تازه بالا اومده عملیات های دیگری روی هارد دیسک در حال انجامه که روی سرعت پاسخگویی تاثیر منفی داره. یا فرضا 20 ثانیه زمان کمی ئه.
مقایسه متد ها به همین سادگی میسر نیست.

بعد اینکه کنترل progress bar در دات نت را چجوری میشه حالتی که نور میده و مدام از چپ به راست میره را کنسل کرد که دیگه نور نده؟ متوجه ی منظورم از نور ، میشید ؟
کنترلش توسط سرویس تم ویندوز ئه، اگه از ظاهر تم چیزی راضی نیستید معمولا باید خودتون طراحیش کنید.
کد:
    using System.Drawing;
    using System.Windows.Forms;

    class NewProgressBar : ProgressBar
    {
        public NewProgressBar()
        {
            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, ClientRectangle);
            var rect = new Rectangle(1, 1, (int)((double)Value / Maximum * (ClientRectangle.Width - 2)), ClientRectangle.Height - 2);
            if (rect.Width > 0)
            {
                ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rect);
            }
        }
    }
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
پردازنده چند نخی هارد دیسک شما رو به چند قسمت مستقل تقسیم نمی کنه، تعداد هد های هارد دیسک رو هم که باید موقع جستجو محتویات سکتور ها رو بخونند افزایش نمیده. معطلی جستجو هم ترکیبی است از معطلی برای پردازنده و حافظه RAM و هارد دیسک. زمانی هم که صرف پاسخگویی پردازنده و جستجوی حافظه RAM و ... میشه نسبت به زمانی که صرف جستجوی فیزیکی در هارد دیسک میشه فوق العاده کمه، اصلا قابل مقایسه نیستند. به همین جهت چیزی که شما رو در عمل معطل میکنه پردازنده نیست، هارد دیسک ئه که هر چند تا پردازنده هم باهاش درگیر باشه فرقی بحالش نمی کنه.
به ماهیت چیزی که دارید ازش اطلاعات میگیرید فکر کنید، فرض کنید فقط یک نفر پاسخگو برای تلفن 118 وجود داشته باشه، نه چند نفر. حالا شما قراره 200 تماس داشته باشید. چه با یک نفر و چه با دویست نفر با 118 تماس برقرار کنید، کمکی به کاهش زمان 200 تماس نمی کنه، چون اون پاسخگو تا به تماس یک نفر پاسخ نده، تماس های باقیمونده معطل میمونه. در مورد جستجو در یک هارد دیسک هم تا حدودی همینطوره و در اغلب موارد هم درگیر کردن هارد دیسک با چند عملیات بجای افزایش زمان کندترش می کنه چون هد ها باید مدام بین مکان های متفاوت جابجا بشن.


ممکنه نهایتا همین نتیجه رو بگیرید اما در کل مقایسه ها رو باید در شرایط یکسان و برای زمان های طولانی اجرا کنید، مثلا اطلاعاتی که قبلا یکبار در حافظه cache شده طبعا در دفعه بعد سریعتر جستجو میشه، چون از cache میخونه، نه هارد دیسک. یا فرضا وقتی ویندوز تون تازه بالا اومده عملیات های دیگری روی هارد دیسک در حال انجامه که روی سرعت پاسخگویی تاثیر منفی داره. یا فرضا 20 ثانیه زمان کمی ئه.
مقایسه متد ها به همین سادگی میسر نیست.

خیلی ممنون.
بله . برای دومین سرچ ، زمان جستجو خیلی کمتر میشد و از مقدار مصرف هارد (برای جستجوی مجدد) ، بسیار کمتر استفاده میشد.
حالا باز هم وقتی از دستوری که گفتم استفاده کردم ، چندان تفاوتی در زمان نکرد.
قضیه ی هد هارد هم میدونم . مثلا فرض کنید برای زمان کش ای که گفتین (که زیاد به هارد ربط نداره) ، در اون صورت ، سرعت اجرای کدی که در چند نخی اجرا بشه ، در پردازنده های چند نخی ، بالاتر خواهد بود؟ چون واسه من زیاد فرق نکرد.

کنترلش توسط سرویس تم ویندوز ئه، اگه از ظاهر تم چیزی راضی نیستید معمولا باید خودتون طراحیش کنید.
کد:
    using System.Drawing;
    using System.Windows.Forms;

    class NewProgressBar : ProgressBar
    {
        public NewProgressBar()
        {
            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, ClientRectangle);
            var rect = new Rectangle(1, 1, (int)((double)Value / Maximum * (ClientRectangle.Width - 2)), ClientRectangle.Height - 2);
            if (rect.Width > 0)
            {
                ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rect);
            }
        }
    }

اه . بخاطر تابع SetStyle و مقدار ControlStyles.UserPaint بود تا متد OnPaint اجرا بشه؟! خیلی ممنون.
من واسه خودم متد OnPaint را override میکردم ، میدیدم این متد اصلا اجرا نمیشه.
اما من مقدار ControlStyles.AllPaintingInWmPaint را متوجه نشدم چی کار میکنه . از توضیحاتش برمیاد که انگار باید پیغام WM_ERASEBKGND را (که زمانی که پشت زمینه ی کنترلی باید پاک بشه ، فرستاده میشه و احتمالا باعث فراخونی متد OnPaintBackground از اون کنترل میشه را) رد میکنه اما بازم این متد OnPaintBackground اجرا میشه . من دقیق متوجه ی این مقدار نشدم که چی کار میکنه؟

تابع ProgressBarRenderer.DrawHorizontalBar ، برای رسم پروگرس بار خالی استفاده میشه و تابع ProgressBarRenderer.DrawHorizontalChunks برای رسم مقدار value یعنی برای رسم پر کردن پروگرس بار؟
اما آخر چی بود که باعث میشد اون نور متحرک توی شی پروگرس بار از چپ به راست بیاد؟ یعنی مثلا اگه خودمون بخوایم بیاریمش دوباره (بدون فعال کردن رسم پیشفرض . یعنی بدون false کردن آرگومان دوم تابع SetStyle) ، باید از کدوم کلاس یا کدوم تابع استفاده کنیم؟

-----------------------

بعد اینکه برای تغییر رنگ پروگرس بار باید چی کار کرد؟
من کد زیر را نوشتم ولی کار نکرد :

کد:
    class CustomProgressBar : ProgressBar
    {
        private Color _progressColor;
        public Color ProgressColor
        {
            get
            {
                return this._progressColor;
            }
            set
            {
                this._progressColor = value;
                this.Invalidate();
            }
        }
     

        public CustomProgressBar()
        {
            // این تابع ، رفتار و حالت یک کنترل را تا حد زیادی میتونه تغییر بده
            // مقدار پر اهمیت UserPaint ، باعث میشه اون کنترل ، خودش را رسم کنه بنابراین باعث میشه که متد OnPaint اون کنترل (که باعث فراخونی رویداد Paint از اون کنترل میشه) ، فراخونی بشه
            // ادامه تابع بالا : پارامتر value در ورودی دوم تابع بالا ، اگه true باشه ، فلگ های انتخا شده رااعمال میکنه وگرنه اعمال نمیکنه.
            this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            // پروپرتی IsSupported باید مقدار true را برگردونه تا توابع DrawHorizontalBar و DrawHorizontalChunks بتونن کار کنن وگرنه خطا پرتاب میکنن.
            if (ProgressBarRenderer.IsSupported == false)
            {
                return;
            }

            // کلاس ProgressBarRenderer ، برای رسم کنترل پروگرس بار کاربرد داره. این متد ، برای رسم پروگرس بار خالی بکار میره
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, this.ClientRectangle);
            var rectForFilling = new Rectangle(1, 1, (int)((double)Value / Maximum * (ClientRectangle.Width - 2)), ClientRectangle.Height - 2);
            if (rectForFilling.Width > 0)
            {
                // تغییر رنگ
                using (Brush brush = new SolidBrush(this.ProgressColor))
                {
                    e.Graphics.FillRectangle(brush, rectForFilling);
                }
             
                // این متد ، برای رسم قسمت پُر پروگرس بار ، یعنی برای رسم مقدار پروپرتی Value کاربرد داره.
                ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rectForFilling);
            }
        }
     
     
    }

بعد هم کد زیر را داخل رویداد شی ای مینویسم :
کد:
            this.customProgressBar1.Value+=5;
            this.customProgressBar1.ProgressColor = Color.SkyBlue;

اما کار نمیکنه!
 
آخرین ویرایش:

the_king

مدیرکل انجمن
قضیه ی هد هارد هم میدونم . مثلا فرض کنید برای زمان کش ای که گفتین (که زیاد به هارد ربط نداره) ، در اون صورت ، سرعت اجرای کدی که در چند نخی اجرا بشه ، در پردازنده های چند نخی ، بالاتر خواهد بود؟ چون واسه من زیاد فرق نکرد.

خوب گوش نمیدید چی میگم، اگر گوش میدادید نباید باز یه سوال مشابه مثل این میکردید. منبع اطلاعات شما کجا است؟ یک حافظه cache، در واقع RAM. شما که یک حافظه cache بیشتر ندارید، حالا هر چند تا نخ هم داشته باشید، اون cache که تعدادش بیشتر نمیشه. چه یک نخ به اون cache رجوع کنه و چه چند نخ بهر حال cache در هر لحظه به یکی شون پاسخگو ئه. سرعت پاسخگویی اش هم در مقایسه با هارد دیسک و واحد بزرگی مثل ثانیه بسیار بالا است، معطلی آنچنانی نداره که حذف کردنش محسوس باشه. باتوجه به فرکانس بالای پردازنده و حافظه RAM طبعا تفاوت محسوسی بدست نمیاد، نباید توقع تفاوت قابل توجهی داشته باشید. در ضمن هیچ اجرای چند نخ ای وجود نداره که اصلا سربار نداشته باشه، سربار گرچه اندک همیشه هست، اگه بیخودی چند نخی اش کنید سربار حتی بجای بهتر کردن بدترش هم میکنه.

اه . بخاطر تابع SetStyle و مقدار ControlStyles.UserPaint بود تا متد OnPaint اجرا بشه؟! خیلی ممنون.
من واسه خودم متد OnPaint را override میکردم ، میدیدم این متد اصلا اجرا نمیشه.
بله، UserPaint برای کنترلی که ترسیم اش توسط سرویس تم ویندوز انجام میشه false ئه و خود کنترل نیازی به درگیر شدن در این مساله نداره.

اما من مقدار ControlStyles.AllPaintingInWmPaint را متوجه نشدم چی کار میکنه . از توضیحاتش برمیاد که انگار باید پیغام WM_ERASEBKGND را (که زمانی که پشت زمینه ی کنترلی باید پاک بشه ، فرستاده میشه و احتمالا باعث فراخونی متد OnPaintBackground از اون کنترل میشه را) رد میکنه اما بازم این متد OnPaintBackground اجرا میشه . من دقیق متوجه ی این مقدار نشدم که چی کار میکنه؟
دو تا مورد متفاوت رو با هم قاطی کردید. WM_ERASEBKGND یک پیام ئه که از طرف درخواست کننده دیگری به پنجره کنترل فرستاده میشه که ازش درخواست میکنه زمینه شو ترسیم کنه. مثل زمانی که پنجره ای از روی کنترل عبور میکنه. اما OnPaintBackground متدی است که فراخوانی میشه تا زمینه کنترل ترسیم بشه. شما با AllPaintingInWmPaint مشخص می کنید که علاقه ای به پاسخگویی به پیام های WM_ERASEBKGND ندارید و داخل کنترل هر زمان لازم باشه OnPaintBackground فراخوانی می شه یا اصلا مستقیما در Paint زمینه رو رسم می کنید، اما AllPaintingInWmPaint به این معنی نیست که میخواهید OnPaintBackground فراخوانی نشه، چون کد کلاس کنترل هم در هنگام رسم OnPaintBackground رو اجرا می کنه که یک متد اساسی کنترل ئه و ربطی هم دریافت کردن و نکردن پیام WM_ERASEBKGND نداره.
تابع ProgressBarRenderer.DrawHorizontalBar ، برای رسم پروگرس بار خالی استفاده میشه و تابع ProgressBarRenderer.DrawHorizontalChunks برای رسم مقدار value یعنی برای رسم پر کردن پروگرس بار؟
بله
اما آخر چی بود که باعث میشد اون نور متحرک توی شی پروگرس بار از چپ به راست بیاد؟ یعنی مثلا اگه خودمون بخوایم بیاریمش دوباره (بدون فعال کردن رسم پیشفرض . یعنی بدون false کردن آرگومان دوم تابع SetStyle) ، باید از کدوم کلاس یا کدوم تابع استفاده کنیم؟
تم ویندوز ئه دیگه، میتونه در هر لحظه روی کنترلی که رسم اش رو برعهده داره کاری انجام بده، مثل چشمک زدن در TextBox یا مثل تغییر رنگ تدریجی Scrollbar ای که ماوس روی اون قرار میگیره یا مثل محو شدن ترسیم های پشت کادر شیشه ای پنجره. میتوانید شبیه سازیش کنید ولی نمونه اصلی رو دیگه نه. چون مدیریت رسم اش رو از سیستم عامل میگیرید دیگه انیمیشنی که خود تم رسم اش کنترل میکنه رو از دست میدید. نمیتوانید به سیستم عامل بگید پنجره این کنترل رو من رسم می کنم ولی تو هم بیا رسم دلخواهت رو داخلش بکن. بالاخره باید تصمیم بگیرید کنترل رو شما رسم می کنید یا سیستم عامل، نمیشه هردو باشن.
بعد اینکه برای تغییر رنگ پروگرس بار باید چی کار کرد؟
من کد زیر را نوشتم ولی کار نکرد :

کد:
    class CustomProgressBar : ProgressBar
    {
        private Color _progressColor;
        public Color ProgressColor
        {
            get
            {
                return this._progressColor;
            }
            set
            {
                this._progressColor = value;
                this.Invalidate();
            }
        }
    

        public CustomProgressBar()
        {
            // این تابع ، رفتار و حالت یک کنترل را تا حد زیادی میتونه تغییر بده
            // مقدار پر اهمیت UserPaint ، باعث میشه اون کنترل ، خودش را رسم کنه بنابراین باعث میشه که متد OnPaint اون کنترل (که باعث فراخونی رویداد Paint از اون کنترل میشه) ، فراخونی بشه
            // ادامه تابع بالا : پارامتر value در ورودی دوم تابع بالا ، اگه true باشه ، فلگ های انتخا شده رااعمال میکنه وگرنه اعمال نمیکنه.
            this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            // پروپرتی IsSupported باید مقدار true را برگردونه تا توابع DrawHorizontalBar و DrawHorizontalChunks بتونن کار کنن وگرنه خطا پرتاب میکنن.
            if (ProgressBarRenderer.IsSupported == false)
            {
                return;
            }

            // کلاس ProgressBarRenderer ، برای رسم کنترل پروگرس بار کاربرد داره. این متد ، برای رسم پروگرس بار خالی بکار میره
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, this.ClientRectangle);
            var rectForFilling = new Rectangle(1, 1, (int)((double)Value / Maximum * (ClientRectangle.Width - 2)), ClientRectangle.Height - 2);
            if (rectForFilling.Width > 0)
            {
                // تغییر رنگ
                using (Brush brush = new SolidBrush(this.ProgressColor))
                {
                    e.Graphics.FillRectangle(brush, rectForFilling);
                }
            
                // این متد ، برای رسم قسمت پُر پروگرس بار ، یعنی برای رسم مقدار پروپرتی Value کاربرد داره.
                ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rectForFilling);
            }
        }
    
    
    }

بعد هم کد زیر را داخل رویداد شی ای مینویسم :
کد:
            this.customProgressBar1.Value+=5;
            this.customProgressBar1.ProgressColor = Color.SkyBlue;

اما کار نمیکنه!
کار نمی کنه چون شما اولا برای ProgressColor باید یک رنگ پیشفرض در نظر بگیرید که اول کار Empty نباشه و رسمش دیده بشه و ثانیا درست بعد از رسم با FillRectangle پشیمون شدید و با DrawHorizontalChunks رویش رسم پیشفرض رو می کنید. خوب اگه قرار باشه با FillRectangle پر اش کنید که دیگه نباید DrawHorizontalChunks روی اون رسم بشه چون رسم زیرش رو از دست میدید.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
خیلی ممنون استاد علی . متوجه شدم.
میخوام 3 مقدار random بگیرم اما هر 3 بار ، عدد متفاوتی بین 0 تا 255 بده ولی نمیدونم چرا در کد زیر با اونکه 3 شی جدید و متفاوتی از هم هستن ولی 3 عدد یکسان میده؟! :


کد:
            Random[] randoms = new Random[3];
            for (int i = 0; i < randoms.Length; i++)
            {
                randoms[i] = new Random(DateTime.Now.Millisecond);
            }

            foreach (Random item in randoms)
            {
                MessageBox.Show(item.Next(0, 255).ToString());
            }

البته میتونم از طریق تابع بازگشتی یا روش هایی از این قبیل ، مشکل را حل کنم ولی سئوالم اینه که کد بالا چرا جواب نمیده و هر عضو از آرایه ، یه مقدار متفاوت را بر نمیگردونه ودقیق ، یک مقادر را برمیگردونه؟! ظاهرا مشکل خاصی نداره.
 
آخرین ویرایش:

the_king

مدیرکل انجمن
خیلی ممنون استاد علی . متوجه شدم.
میخوام 3 مقدار random بگیرم اما هر 3 بار ، عدد متفاوتی بین 0 تا 255 بده ولی نمیدونم چرا در کد زیر با اونکه 3 شی جدید و متفاوتی از هم هستن ولی 3 عدد یکسان میده؟! :


کد:
            Random[] randoms = new Random[3];
            for (int i = 0; i < randoms.Length; i++)
            {
                randoms[i] = new Random(DateTime.Now.Millisecond);
            }

            foreach (Random item in randoms)
            {
                MessageBox.Show(item.Next(0, 255).ToString());
            }

البته میتونم از طریق تابع بازگشتی یا روش هایی از این قبیل ، مشکل را حل کنم ولی سئوالم اینه که کد بالا چرا جواب نمیده و هر عضو از آرایه ، یه مقدار متفاوت را بر نمیگردونه ودقیق ، یک مقادر را برمیگردونه؟! ظاهرا مشکل خاصی نداره.
Random عدد تصادفی نیست، شیء سازنده اعداد تصادفی است، اگر میخواهید 100 تا عدد تصادفی بسازید فقط به یک شیء Random نیاز دارید، نه 100 تا.
اون seed ای که موقع ساختن Random بهش میدید کارو خراب می کنه.
استفاده از Random یک قاعده کلی داره، شما برای کل برنامه یا حداقل برای کل کلاس تون فقط و فقط یک شی از کلاس Random و بدون هیچ پارامتر ورودی میسازید که خودش بر اساس زمان فعلی seed انتخاب میکنه و حتی static بودنش متغیرش هم برای مواقعی که از کلاس تون چند تا شی میسازید خوبه و بعد هر چقدر عدد که دلتون خواست از همون یک شیء دریافت می کنید. وگرنه اگر قرار باشه چند تا شی از Random بسازید که همه شون seed شون با یک مقدار یکسان تنظیم بشه، طبعا مقدار خروجی شون هم مشابه میشه دیگه. شما که توقع ندارید در یک حلقه به اون کوچیکی DateTime.Now.Millisecond فرصت تغییر پیدا کنه، خیلی به ندرت پیش میاد که وسط همچین حلقه ای مقدار DateTime.Now.Millisecond عوض شه.
استفاده از یک شیء برای تولید اعداد تصادفی این مزیت رو داره که توزیع رقم های تصادفی یکنواخت خواهد بود، چون یکی از ویژگی هایی که الگوریتم تولید کننده اعداد تصادفی باید رعایت کنه همینه که توزیع یکنواخت باشه، فرضا بین اعداد 1 تا 100 تمایلش به تولید اعداد بیشتر از 50 چند برابر تمایلش به تولید اعداد کمتر از 50 نباشه. اگر از چند شی برای تولید اعداد تصادفی استفاده کنید احتمال داره این توزیع یکنواخت رو بهم بزنید.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
Random عدد تصادفی نیست، شیء سازنده اعداد تصادفی است، اگر میخواهید 100 تا عدد تصادفی بسازید فقط به یک شیء Random نیاز دارید، نه 100 تا.
اون seed ای که موقع ساختن Random بهش میدید کارو خراب می کنه.
استفاده از Random یک قاعده کلی داره، شما برای کل برنامه یا حداقل برای کل کلاس تون فقط و فقط یک شی از کلاس Random و بدون هیچ پارامتر ورودی میسازید که خودش بر اساس زمان فعلی seed انتخاب میکنه و حتی static بودنش متغیرش هم برای مواقعی که از کلاس تون چند تا شی میسازید خوبه و بعد هر چقدر عدد که دلتون خواست از همون یک شیء دریافت می کنید. وگرنه اگر قرار باشه چند تا شی از Random بسازید که همه شون seed شون با یک مقدار یکسان تنظیم بشه، طبعا مقدار خروجی شون هم مشابه میشه دیگه. شما که توقع ندارید در یک حلقه به اون کوچیکی DateTime.Now.Millisecond فرصت تغییر پیدا کنه، خیلی به ندرت پیش میاد که وسط همچین حلقه ای مقدار DateTime.Now.Millisecond عوض شه.
استفاده از یک شیء برای تولید اعداد تصادفی این مزیت رو داره که توزیع رقم های تصادفی یکنواخت خواهد بود، چون یکی از ویژگی هایی که الگوریتم تولید کننده اعداد تصادفی باید رعایت کنه همینه که توزیع یکنواخت باشه، فرضا بین اعداد 1 تا 100 تمایلش به تولید اعداد بیشتر از 50 چند برابر تمایلش به تولید اعداد کمتر از 50 نباشه. اگر از چند شی برای تولید اعداد تصادفی استفاده کنید احتمال داره این توزیع یکنواخت رو بهم بزنید.

خیلی ممنون استاد علی
اون Seed هم برداشتم ؛ یک شی Random هم ساختم ولی بازم همین جوری بود . هر بر ، 3 عدد یکسان میده!
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
کدی که نوشتید رو درج کنید، ندیده که نمیتونم ایراد کدتون رو بگم.

خیلی ممنون
ببخشید ، درست شد . کد زیر مشکل نداره . نمیدونم چرا قبلا بدون seed هم آزمایش کرده بودم ، مشکل داشت ولی الان نداره (شاید اون موقع ، یک شی را نساختم)



کد:
Random random = new Random();
            int[] randomResaults = new int[3];
            for (int i = 0; i < randomResaults.Length; i++)
            {
                randomResaults[i] = random.Next(0, 255);
            }

الان این کد مشکل نداره .
ممنون
 

the_king

مدیرکل انجمن
خیلی ممنون
ببخشید ، درست شد . کد زیر مشکل نداره . نمیدونم چرا قبلا بدون seed هم آزمایش کرده بودم ، مشکل داشت ولی الان نداره (شاید اون موقع ، یک شی را نساختم)



کد:
Random random = new Random();
            int[] randomResaults = new int[3];
            for (int i = 0; i < randomResaults.Length; i++)
            {
                randomResaults[i] = random.Next(0, 255);
            }

الان این کد مشکل نداره .
ممنون
فقط یادآوری کنم که Next(0, 255) محدوده کامل byte نیست و هیچوقت 255 را تولید نخواهد کرد و حداکثر 254 خواهد شد.
Untitled.jpg
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
خیلی ممنون استاد علی
میگم یه چیز عجیب!
در کد زیر :


کد:
    class CustomButton : Control
    {
        Bitmap Bitmap;
        public CustomButton()
        {
            //this.Bitmap = new Bitmap(this.Width, this.Height);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            int width = this.Width;
            e.Graphics.FillRectangle(Brushes.Red, new Rectangle(new Point(20, 5), this.Size));
            e.Graphics.DrawString("salam", this.Font, Brushes.Blue, new Point(40, 10));
        }

    }

و ساختن شی اش :

کد:
            CustomButton customButton = new CustomButton();
            customButton.Bounds = new Rectangle(new Point(5, 5), new Size(120, 35));
           
            this.panel1.Controls.Add(customButton);

وقتی اون کد در متد سازنده را کامنت کنیم ، مشکلی نیست و در این صورت ، خط اول در متد OnPaint ، یعنی this.Width ، مقداری که مشخص کردیم ، یعنی مقدار 120 را داره . اما وقتی اون کامنت در متد سازنده را برداریم ، در اجرای متد سازنده ، به ارور برمیخوریم چون مقدار this.Width را صفر میگیره!! با اونکه مقدارش را 120 مشخص کردیم !
چرا؟! من اولین باره همچین چیزی میبینم! مقدار customButton.Width را هم اگه جداگانه بدیم اما وقتی اون خط در متد سازنده را از کامنت برداریم ، باز هم ارور میده!!
بعد اینکه در همین کلاس CustomButton ، وقتی بجای Control ، از کلاس Button ارث بری کنیم ، و اون کامنت را هم برداریم ، هیچ مشکلی نیست.


بعد اینکه میشه یه دکمه ، با پشت زمینه ی transparent ایجاد کرد؟ یعنی پشت زمینه ی کنترلی که اون دکمه توش هست ، دیده بشه. من کد این صفحه را برای خودم تست کردم ولی دکمه ام سیاه شد بجای transparent :

Drawing a transparent button
 

the_king

مدیرکل انجمن
خیلی ممنون استاد علی
میگم یه چیز عجیب!
در کد زیر :


کد:
    class CustomButton : Control
    {
        Bitmap Bitmap;
        public CustomButton()
        {
            //this.Bitmap = new Bitmap(this.Width, this.Height);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            int width = this.Width;
            e.Graphics.FillRectangle(Brushes.Red, new Rectangle(new Point(20, 5), this.Size));
            e.Graphics.DrawString("salam", this.Font, Brushes.Blue, new Point(40, 10));
        }

    }

و ساختن شی اش :

کد:
            CustomButton customButton = new CustomButton();
            customButton.Bounds = new Rectangle(new Point(5, 5), new Size(120, 35));
          
            this.panel1.Controls.Add(customButton);

وقتی اون کد در متد سازنده را کامنت کنیم ، مشکلی نیست و در این صورت ، خط اول در متد OnPaint ، یعنی this.Width ، مقداری که مشخص کردیم ، یعنی مقدار 120 را داره . اما وقتی اون کامنت در متد سازنده را برداریم ، در اجرای متد سازنده ، به ارور برمیخوریم چون مقدار this.Width را صفر میگیره!! با اونکه مقدارش را 120 مشخص کردیم !
چرا؟! من اولین باره همچین چیزی میبینم! مقدار customButton.Width را هم اگه جداگانه بدیم اما وقتی اون خط در متد سازنده را از کامنت برداریم ، باز هم ارور میده!!
مورد عجیبی وجود نداره، بجز کد شما. شما اگر از کلاس Control یک شیء بسازید، می بینید که یک مشخصه Width داره و مقدار پیشفرض Width اش هم مثل هم متغیر های int صفر ئه :
کد:
        private void button1_Click(object sender, EventArgs e)
        {
            var c = new Control();
            MessageBox.Show(c.Width.ToString());
        }
و کسی هم نمیتونه ادعا کنه میتونه به مشخصه Width همچین شیء ای مقدار اولیه ای داده باشه، قبل از اینکه متد سازنده کلاس اجرا شده باشه. اگر مقدار پیشفرض Width صفر باشه، در متد سازنده کلاس اش هم صفر ئه :
کد:
        private void button1_Click(object sender, EventArgs e)
        {
            var c = new MyControl();
        }

        private class MyControl : Control
        {
            public MyControl()
            {
                MessageBox.Show(this.Width.ToString());
            }
        }
کد ;this.Bitmap = new Bitmap(this.Width, this.Height) از کسی که نقش و اولویت متد سازنده کلاس رو درک کرده بعیده. چطور ممکنه شیء ای که تازه میخواد ساخته بشه با Width ئه 120 اجرای متد سازنده کلاس رو شروع کنه. اون Width با مقدار 120 بعدا داده شده، بعد از اینکه متد سازنده کلاس تون اجرا شده و شیء ساخته شده. و OnPaint هم یک رخدادی است که ممکنه در زمان های مختلفی رخ بده، چه قبل از اینکه مقدار Width از صفر تغییر کنه و چه بعد از مقدار دهی Width

بعد اینکه در همین کلاس CustomButton ، وقتی بجای Control ، از کلاس Button ارث بری کنیم ، و اون کامنت را هم برداریم ، هیچ مشکلی نیست.
به این خاطر که DefaultSize رو اضافه کردن تا هر نوع کنترلی فرضا Button بتونه اندازه پیشفرض خودش رو برای Size تعریف کنه تا دیگه با طول و عرض صفر شروع نکنه :
کد:
        private void button1_Click(object sender, EventArgs e)
        {
            var c = new MyControl();
        }

        private class MyControl : Control
        {
            public MyControl()
            {
                MessageBox.Show(this.Width.ToString());
            }

            protected override Size DefaultSize => new Size(100, 35);
        }

بعد اینکه میشه یه دکمه ، با پشت زمینه ی transparent ایجاد کرد؟ یعنی پشت زمینه ی کنترلی که اون دکمه توش هست ، دیده بشه. من کد این صفحه را برای خودم تست کردم ولی دکمه ام سیاه شد بجای transparent :

Drawing a transparent button
همچین چیزی نداریم، میبینید که در کنترل های استاندارد فرم هم نیست. چون جز ویژگی های اساسی کنترل های ویندوز نیست، برای همین بصورت کامل و بی نقص، نه. کاری که عملا انجام میشه، دو روش بیشتر نیست. یا عکسی قبل از قرار گرفتن کنترل رو معیار قرار میدن و داخل پنجره کنترل مدام رسمش می کنن که طبعا ممکنه عکس بروز و هماهنگ با تغییرات نباشه یا دائم تمامی کنترل های زیرش رو مجددا در پنجره کنترل رسم می کنن که میتونه منجر به پر پر زدن و ناسازگاری با بعضی کنترل ها بشه. هر دو حالت هم معایب و مزایای خاص خودش رو داره. بهترین حالت رو وقتی دارید که همه کنترل های روی فرم رو خودتون ساخته باشید و بتوانید شیشه ای شدن زمینه رو بین شون هماهنگ کنید، یعنی همه کنترل ها رو خودتون رسم کنید. اما در کل زمینه شیشه ای برای کنترل های فرم ویندوز یک شبیه سازیه، واقعی نیست.
مشکل در هم رنگ کردن پشت زمینه پیکچر باکس بر روی فرم و سایر کنترل ها
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
مورد عجیبی وجود نداره، بجز کد شما. شما اگر از کلاس Control یک شیء بسازید، می بینید که یک مشخصه Width داره و مقدار پیشفرض Width اش هم مثل هم متغیر های int صفر ئه :
کد:
        private void button1_Click(object sender, EventArgs e)
        {
            var c = new Control();
            MessageBox.Show(c.Width.ToString());
        }
و کسی هم نمیتونه ادعا کنه میتونه به مشخصه Width همچین شیء ای مقدار اولیه ای داده باشه، قبل از اینکه متد سازنده کلاس اجرا شده باشه. اگر مقدار پیشفرض Width صفر باشه، در متد سازنده کلاس اش هم صفر ئه :
کد:
        private void button1_Click(object sender, EventArgs e)
        {
            var c = new MyControl();
        }

        private class MyControl : Control
        {
            public MyControl()
            {
                MessageBox.Show(this.Width.ToString());
            }
        }
کد ;this.Bitmap = new Bitmap(this.Width, this.Height) از کسی که نقش و اولویت متد سازنده کلاس رو درک کرده بعیده. چطور ممکنه شیء ای که تازه میخواد ساخته بشه با Width ئه 120 اجرای متد سازنده کلاس رو شروع کنه. اون Width با مقدار 120 بعدا داده شده، بعد از اینکه متد سازنده کلاس تون اجرا شده و شیء ساخته شده. و OnPaint هم یک رخدادی است که ممکنه در زمان های مختلفی رخ بده، چه قبل از اینکه مقدار Width از صفر تغییر کنه و چه بعد از مقدار دهی Width


به این خاطر که DefaultSize رو اضافه کردن تا هر نوع کنترلی فرضا Button بتونه اندازه پیشفرض خودش رو برای Size تعریف کنه تا دیگه با طول و عرض صفر شروع نکنه :
کد:
        private void button1_Click(object sender, EventArgs e)
        {
            var c = new MyControl();
        }

        private class MyControl : Control
        {
            public MyControl()
            {
                MessageBox.Show(this.Width.ToString());
            }

            protected override Size DefaultSize => new Size(100, 35);
        }

:green:
خیلی ممنون . به قول فامیل مون که پروفسورم :green:
اما قضیه ی اون DefaultSize را دقیق متوجه نشدم . اشکال نداره.


همچین چیزی نداریم، میبینید که در کنترل های استاندارد فرم هم نیست. چون جز ویژگی های اساسی کنترل های ویندوز نیست، برای همین بصورت کامل و بی نقص، نه. کاری که عملا انجام میشه، دو روش بیشتر نیست. یا عکسی قبل از قرار گرفتن کنترل رو معیار قرار میدن و داخل پنجره کنترل مدام رسمش می کنن که طبعا ممکنه عکس بروز و هماهنگ با تغییرات نباشه یا دائم تمامی کنترل های زیرش رو مجددا در پنجره کنترل رسم می کنن که میتونه منجر به پر پر زدن و ناسازگاری با بعضی کنترل ها بشه. هر دو حالت هم معایب و مزایای خاص خودش رو داره. بهترین حالت رو وقتی دارید که همه کنترل های روی فرم رو خودتون ساخته باشید و بتوانید شیشه ای شدن زمینه رو بین شون هماهنگ کنید، یعنی همه کنترل ها رو خودتون رسم کنید. اما در کل زمینه شیشه ای برای کنترل های فرم ویندوز یک شبیه سازیه، واقعی نیست.
مشکل در هم رنگ کردن پشت زمینه پیکچر باکس بر روی فرم و سایر کنترل ها

اه . راهکار قشنگی هه .
الان پس میشه بجای رسم حالت معمولی ، اول از اون بخش از کنترلِ کانتینرِ اون دکمه (اون بخش از کنترلی که اون دکمه توش قرار داره) ، عکس گرفت و بعد اون رو توی paint مربوط به اون دکمه ، رسم کرد؟ راهکار جالبی هه . اما مشکل این حالت که میگفتین ، چیه؟
بعد اینکه پس چرا رسم درکنترل picturebox مشکلی نداره و به راحتی رسم و transparent میشه؟ چرا در کنترل های دیگه مشکل داره؟ احتمالا ، بجای دکمه ، از شی picturebox استفاده میکنم.
ممنون
 

the_king

مدیرکل انجمن
:green:
اما قضیه ی اون DefaultSize را دقیق متوجه نشدم . اشکال نداره.
خیلی ساده است، Control می توانست برای Size یک مقدار پیشفرض ثابت در نظر بگیره، ولی اونوقت دیگه وارث های Control در تغییر مقدار پیشفرض به مشکل بر میخوردند.
برای اینکه این مشکل پیش نیاد و وارث بتونه مقادیر پیشفرض رو تغییر بده، سازنده کلاس مقدار پیشفرض مشخصه رو در متد سازنده با کد نویسی تعیین می کنه، اما بجای مقدار ثابتی که میتوانست بهش بده مقدار رو از یک مشخصه read only یا متد گرانبار پذیر protected میخونه. از اونجایی که اون مشخصه یا متد مثل DefaultSize در وارث قابل تغییره، وارث در صورت تمایل میتونه مقدار دیگری رو بجای مقدار پیشفرض تعیین کنه. اگر چیزی مثل DefaultSize تعریف نشده بود نمی توانستید Size پیشفرض خودتون رو در هنگام ایجاد شدن شیء تعیین کنید.
اه . راهکار قشنگی هه .
الان پس میشه بجای رسم حالت معمولی ، اول از اون بخش از کنترلِ کانتینرِ اون دکمه (اون بخش از کنترلی که اون دکمه توش قرار داره) ، عکس گرفت و بعد اون رو توی paint مربوط به اون دکمه ، رسم کرد؟ راهکار جالبی هه . اما مشکل این حالت که میگفتین ، چیه؟

گفتم مثالش رو. در ضمن مشکل یکی دو تا نیست. اولا وقتی کنترل در جایی قرار گرفت دیگه دسترسی به تصویری از زیرش دشواره و بیشتر مناسب وقتیه که هنوز کنترل روی اون قسمت قرار نگرفته. ثانیا اگر زیرش تغییری رخ بده آگاه شدن از تغییرات و اعمال تغییرات خودش دردسر سازه.
بعد اینکه پس چرا رسم درکنترل picturebox مشکلی نداره و به راحتی رسم و transparent میشه؟ چرا در کنترل های دیگه مشکل داره؟ احتمالا ، بجای دکمه ، از شی picturebox استفاده میکنم.
ممنون
متوجه نمیشم چی میگید، رسم با رنگ Transparent رو که با زمینه Transparent اشتباه نمی گیرید؟
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
خیلی ساده است، Control می توانست برای Size یک مقدار پیشفرض ثابت در نظر بگیره، ولی اونوقت دیگه وارث های Control در تغییر مقدار پیشفرض به مشکل بر میخوردند.
برای اینکه این مشکل پیش نیاد و وارث بتونه مقادیر پیشفرض رو تغییر بده، سازنده کلاس مقدار پیشفرض مشخصه رو در متد سازنده با کد نویسی تعیین می کنه، اما بجای مقدار ثابتی که میتوانست بهش بده مقدار رو از یک مشخصه read only یا متد گرانبار پذیر protected میخونه. از اونجایی که اون مشخصه یا متد مثل DefaultSize در وارث قابل تغییره، وارث در صورت تمایل میتونه مقدار دیگری رو بجای مقدار پیشفرض تعیین کنه. اگر چیزی مثل DefaultSize تعریف نشده بود نمی توانستید Size پیشفرض خودتون رو در هنگام ایجاد شدن شیء تعیین کنید.

گفتم مثالش رو. در ضمن مشکل یکی دو تا نیست. اولا وقتی کنترل در جایی قرار گرفت دیگه دسترسی به تصویری از زیرش دشواره و بیشتر مناسب وقتیه که هنوز کنترل روی اون قسمت قرار نگرفته. ثانیا اگر زیرش تغییری رخ بده آگاه شدن از تغییرات و اعمال تغییرات خودش دردسر سازه.

متوجه نمیشم چی میگید، رسم با رنگ Transparent رو که با زمینه Transparent اشتباه نمی گیرید؟

ممنون
زمینه ی transparent منظورمه .
مثل یه همچین کدی :


کد:
            PictureBox currentPictureBox = sender as PictureBox;
            e.Graphics.DrawEllipse(new Pen(Color.Red, 4f), new Rectangle(new Point(0, 0), new Size(currentPictureBox.ClientSize.Width, currentPictureBox.ClientSize.Height)));

اما حالا در متد OnPaint مربوط به PictureBox . کد بالا ، در رویداد Paint مربوط به PictureBox بود.
باعث میشه بقیه ی جاهاش که پر رنگ آمیزی نشده (مثل وسط دایره در کد بالا) ، transparent و شفاف بمونه و پشت کنترلش مشخص باشه . اما همچین چیزی برای کنترل دکمه ، امکان پذیر نیست (البته با Flat کردن Style اش تا حدودی امکان پذیر هست ولی مشکلات خودش را داره)
 

جدیدترین ارسال ها

بالا