PDA

View Full Version : Threading Problem



hdv212
چهارشنبه 24 مرداد 1386, 16:54 عصر
سلام و خسته نباشید
من یه فرم دارم که وقتی باز میشه، اطلاعات اون باید لود بشه، چون این کار ممکنه زیاد طول بکشه، میخوام یه waiting form به کاربر نشون بدم که در حال لود شدن هستش، من از Threading استفاده کردم، مشکلی نیست ولی وقتی ازکل کار اون thread انجام میشه، کل فرم و اصلا برنامه م focus رو از دست میده و میره پشت پنجره های ویندوزم قرار میگیره، علت چیه ؟ من از backGroundWorker هم استفاده کردم، دیدم اصلا مشکل فنی باهاش دارم، error مربوط به CrossThread میده و میگه این thread قصد دسترسی به dataGridView رو داره که قبلا توسط thread دیگری (که احتمالا thread اصلی برنامه س) ساخته شده. کسی تا حالا تونسته یه فرم لودینگ رو به کاربر نشون بده، من خودم قبلا این کار رو کردم، شیوه ی خودم مشکلی نداشت ولی روی کامپیوتر های دیگه مشکل داره. حالا اینم کدمه :

private void Frm_Land_Load(object sender, EventArgs e)
{
System.Threading.Thread th = new System.Threading.Thread(gu.Show_WaitingForm);
Application.DoEvents();
th.Start("در حال بار گذاری اطلاعات ...");

this.Initialize_Data();
this.txt_date.Text = this.sh.Show_Hijri_System();
this.toolStripStatusLabel1.Text = "آماده";

Application.DoEvents();
gu.Close_WaitingForm();
}

PC2st
چهارشنبه 24 مرداد 1386, 18:54 عصر
دقیقا نفهمیدم مشکل در ترد بود یا در نمایش فرم...
اگر میخواهید که یک Form رو بالای فرمهای دیگه بیارید:


this.TopMost = true;
this.TopMost = false;



error مربوط به CrossThread میده
در اینحالت از متد Invoke استفاده کنید.

hdv212
چهارشنبه 24 مرداد 1386, 20:22 عصر
ببینید این کد منه برای نشون دادن فرم loading که یه فرم ساده است و یه progressBar :

public void Show_WaitingForm(object message)
{
frm.Text = frm.marqueeProgressBarControl1.Text = message.ToString();
frm.ShowDialog();
}

public void Hide_WaitingForm()
{
if (frm.InvokeRequired)
{
frm.BeginInvoke(new MethodInvoker(Hide_WaitingForm), null);
}
else
{
frm.Close();
}
}

و این هم کدی هست که در رویداد load فرم اصلیم نوشتم :


private void Frm_Land_Load(object sender, EventArgs e)
{
System.Threading.Thread th = new System.Threading.Thread(gu.Show_WaitingForm);
Application.DoEvents();
th.Start("در حال بار گذاری اطلاعات ...");

this.Initialize_Data();
this.txt_date.Text = this.sh.Show_Hijri_System();
this.toolStripStatusLabel1.Text = "آماده";

Application.DoEvents();
gu.Close_WaitingForm();
}

همانطور که میبینید قبل از عمل لود کردن داده ها توسط متدی که خودم نوشتم یعنی (Initialize_Data()) یه آبجکتی به نام gu ساختم که فرم loading رو نمایش میده (البته در یه thread جداگانه و توسط متد Show_WaitingForm) و در خط آخر رویداد load، متد Close_WaitingForm() فراخوانی میشه تا فرم loading بسته بشه، تا حالا عملیات به خوبی انجام شده، فقط کل فرمم و برنامم میره پشت برنامه ها قرار میگیره، احتمال میدم یه جای کار این thread برنامم یا فرمم رو از حالت focus خارج میکنه، شما ببینید میتونید راه حلی براش پیدا کنید، مرسی.

PC2st
چهارشنبه 24 مرداد 1386, 21:14 عصر
فقط کل فرمم و برنامم میره پشت برنامه ها قرار میگیره، احتمال میدم یه جای کار این thread برنامم یا فرمم رو از حالت focus خارج میکنه، شما ببینید میتونید راه حلی براش پیدا کنید، مرسی.
برای اینکه یک فرم دوباره به بالای دیگر برنامه ها برگرده، یعنی اول TopMost رو true و سریعا اون رو false میکنیم. با اینکار فرم بالای بقیه برنامه ها میآد و TopMost اش هم false میشه. مثلا در رویداد Shown بنویسید:


private void Frm_Land_Shown(object sender, EventArgs e)
{
this.TopMost=True;
this.TopMost=false;
}
راستی در کدهای فرم splash screen متد Close_WaitingForm وجود نداشت؟ متدش Hide_WaitingForm بود!

hdv212
چهارشنبه 24 مرداد 1386, 23:31 عصر
PC2st.ir عزیز ممنون از لطفت، بازم کارم راه نیفتاد، ولی قبل از اینکه trread، استارت بشه این کد رو نوشتم :

this.TopMost=True;
و در خط آخر رویداد load فرمم این کد رو ننویسم :

this.TopMost=false;

درست کار میکنه، یعنی فعلا درست کار میکنه، باید روی سیستمهای دیگه هم تست کنم ببینم اونجا چه رفتاری نشون میده، به هر حال مرسی.

PC2st
سه شنبه 30 مرداد 1386, 13:58 عصر
ممکنه دیر شده باشه :ناراحت: ولی فهمیدم چرا اینطوری شده، باید بصورت زیر بنویسیم:


this.TopMost=True;
System.Threading.Thread.Sleep(100);
this.TopMost=false;

چون برای اینکه فرم به بالای بقیه فرم ها بیاد، ممکنه نیاز به پردازش داشته باشه، پس یک توقف کوچک باعث میشه که عملیات به پایان برسه و بعد از آن، TopMost اش رو false میکنیم. (البته کدها برای رویداد Shown هستش)

hdv212
سه شنبه 30 مرداد 1386, 15:35 عصر
PC2st.ir عزیز ممنون از لطفت، من هم به همین نتیجه رسیدم و همین کار رو کردم، ولی بعضی وقتها دوباره میره پشت فرم های برنامم، تازه بعضی وقتها یه مشکل اساسی دیگه هم میخورم، همانطور که در پست دوم من میبینید من دو تابع دارم برای نمایش و بستن فرم loading، همونجا نحوه ی استفاده از اونها رو نوشتم (در رویداد Frm_Land_Load)، حالا یه بار که فرم رو باز میکنم و میبندم، دفعه ی دوم به من error میده که نمیتونه به آبجکتی که dispose شده دسترسی داشته باشه، این خطا در خط frm.ShowDialog(); اتفاق میفته، حالا من متد Show_WaitingForm به صورت زیر تغییر دادم :

public void Show_WaitingForm(object message)
{
if (this.frm == null)
this.frm = new Frm_Waiting();
frm.Text = frm.marqueeProgressBarControl1.Text = message.ToString();
frm.ShowDialog();
}
که چک کنه اگر آبجکت frm برابر با null بود، یه نمونه ی جدید بسازه، ولی باز هم بعضی وقتها همون خطا رو میگیره، انگاری frm نمونه سازی نمیشه، نمیدونم مشکل کجاست. از خاصیت isDisposed هم استفاده کردم که از این بدتر میشد، این یکی کمتر خطا میده نمیدونم چرا یه دفعه میده، 20 دفعه نمیده.

mehdi.mousavi
سه شنبه 30 مرداد 1386, 16:18 عصر
ممکنه دیر شده باشه :ناراحت: ولی فهمیدم چرا اینطوری شده، باید بصورت زیر بنویسیم:


this.TopMost=True;
System.Threading.Thread.Sleep(100);
this.TopMost=false;
چون برای اینکه فرم به بالای بقیه فرم ها بیاد، ممکنه نیاز به پردازش داشته باشه، پس یک توقف کوچک باعث میشه که عملیات به پایان برسه و بعد از آن، TopMost اش رو false میکنیم. (البته کدها برای رویداد Shown هستش)

سلام.
متاسفانه باید خدمتتون عرض کنم که اینطور کد نوشتن صحیح نیست. برای همزمانی دو Thread، باید از Object هایی استفاده کنید که در سطح Kernel کنترل میشن و دسترسی به اونها از دو Thread بلا مانع هستش. به این Object ها، Synchronization Object میگن. مثل Mutex، Semaphore، Event و... که هر یک کاربرد خاص خودشون رو دارن.

تنها حالتی که استفاده از Sleep بلامانع هستش، استفاده از Sleep(0) برای Force کردن Context Switching بین Thread هاست که در استفاده از اون هم باید نکات دیگه ای رو در نظر گرفت.

خدمت hdv212 هم باید عرض کنم که استفاده از Application.DoEvents به شکلی که شما استفاده کردین، بسیار خطرناکه و نباید از این تابع به این شکل استفاده کرد. چون این تابع باعث پردازش پیامهایی میشه که در حال حاضر توی Message Queue قرار دارن. این پیامها هر چیزی میتونن باشن و ... اگر هدفتون فقط Responsive نگه داشتن UI هست، باید با IFilterMessage فقط WM_PAINT ها رو توسط DoEvents پردازش کنید. در غیر اینصورت، اینکار بسیار خطرناکه.

PC2st
سه شنبه 30 مرداد 1386, 16:50 عصر
حالا یه بار که فرم رو باز میکنم و میبندم، دفعه ی دوم به من error میده که نمیتونه به آبجکتی که dispose شده دسترسی داشته باشه، این خطا در خط frm.ShowDialog(); اتفاق میفته

وقتی خطای Cannot access a disposed object رو میده، فهمیدم مشکلش چیه!

ببینید، وقتی که صدا زدن متد Show_WaitingForm رو در یک thread جداگانه ای انجام میدیم، قبول دارید که احتمالش هست این متد مثلا 0.1 ثانیه کندتر صدا زده بشه، درسته؟

خب بعدش چند خط پائینتر میایم و متد Close_WaitingForm رو صدا میزنیم، آیا قبول دارید که امکانش هست این متد زودتر از متد Show_WaitingForm صدا زده بشه؟ چون هر کدام از این دو متد در یک thread جداگانه قرار دارند و ممکنه یک thread سریعتر از thread دیگر اجرا بشه. پس هر وقت متد Close_WaitingForm زودتر از متد Show_WaitingForm اجرا بشه، شیئ frm هم Dispose میشه، و این یعنی اینکه متد Show_WaitingForm دیگه نمیتونه از frm استفاده کنه، چون قبلش توسط متد Close_WaitingForm ، از بین رفته!

اما اینکه متد Close_WaitingForm زودتر از Show_WaitingForm اجرا بشه، بستگی داره، همانطور که میدونید متد Initialize_Data یک حجم زیادی از کارها رو انجام میده و این عمل هم وقت گیره، پس چون این عمل وقت گیره، پس متد Close_WaitingForm، دیر صدا زده میشه و میشه شانس زیادی رو قائل شد که متد Show_WaitingForm قبل از متد Close_WaitingForm اجرا بشه... اما اگر متد Initialize_Data کارش رو خیلی سریع انجام بده (یا اصلا کاری انجام نده)، در این صورت متد Close_WaitingForm زودتر از متد Show_WaitingForm اجرا میشه. دلیل اینکه این دو متد میتوانند قبل یا بعد از هم اجرا بشن، دلیلش بر میگرده به اینکه متد Show_WaitingForm و Close_WaitingForm، هر کدوم در یک thread جداگانه (جدا از هم) قرار دارند... :-)

PC2st
سه شنبه 30 مرداد 1386, 17:16 عصر
جناب mehdi6755 از نکات بسیار مفیدتون ممنونم. در رابطه با متد Sleep دقیقا متوجه منظورتون نشدم.
من بعضی مواقع مجبورم از Sleep استفاده کنم، مثلا کد زیر رو یکبار بدون Sleep و بار بعد همراه با Sleep در رویداد کلیک از یک دکمه قرار دهید، تفاوتشون در اینه که در کد اولی، فرم بدون افکت ظاهر میشه ولی در کد دوم، بدلیل وجود یک توقف کوچک، فرم فرصت داره تا با افکت ظاهر بشه... هر دو حالت رو امتحان کنید تا منظورم رو بهتر بفهمید.


کد (برای حالت اول، متد Sleep رو ور دارید و برای حالت دوم با متد Sleep اون رو اجرا کنید):


Form frm = new Form();
frm.WindowState = FormWindowState.Minimized;
frm.Show();
System.Threading.Thread.Sleep(0);
frm.WindowState = FormWindowState.Normal;


ولی فکر کنم منظورتون این است که فرستادن پارامتر غیر از 0 به متد Sleep کار جالبی نیست، اگر اینطور باشه، من تا حد امکان سعی میکنم که پارامتری غیر از 0 را به متد Sleep ارسال نکنم و ممنونم از این نکته ای که گفتید.

hdv212
سه شنبه 30 مرداد 1386, 19:16 عصر
خدمت hdv212 هم باید عرض کنم که استفاده از Application.DoEvents به شکلی که شما استفاده کردین، بسیار خطرناکه و نباید از این تابع به این شکل استفاده کرد. چون این تابع باعث پردازش پیامهایی میشه که در حال حاضر توی Message Queue قرار دارن. این پیامها هر چیزی میتونن باشن و ... اگر هدفتون فقط Responsive نگه داشتن UI هست، باید با IFilterMessage فقط WM_PAINT ها رو توسط DoEvents پردازش کنید. در غیر اینصورت، اینکار بسیار خطرناکه
mehdi6755 عزیز ممنون از راهنمایی خوبت، ولی دلیل استفاده من از متد DoEvent() رو با یه مثال بیان میکنم، شما یه فرم بساز و مثلا یه statusStrip و یه button بهش اضافه کن و در رویداد click دکمه ات، این کد رو بنویس :

for (int i = 0; i <= 1000; i++)
{
this.toolStripStatusLabel1.Text = i.ToString();
}
این کد در داخل حلقه مقدار جاری متغیر i رو در خاصیت Text مربوط به آبجکت toolStripStatusLabel1 قرار میده، یعنی قاعدتا بعد از هر بار اجرا شدن کد داخل حلقه، آبجکت toolStripStatusLabel1 باید مقدار جدیدی رو نشون بده، ولی تا زمانی که عمل شمردن به پایان نرسیده، toolStripStatusLabel1.Text بروز رسانی نمیشه و فقط یه مقدار 1000 رو نشون میده، ولی با اصلاح کد بالا به این شکل :

for (int i = 0; i <= 1000; i++)
{
Application.DoEvents();
this.toolStripStatusLabel1.Text = i.ToString();
}
به درستی عملیات update شدن ui و نمایش مقدار toolStripStatusLabel1.Text بعد از هر مقدار دهی به درستی انجام میشه.


اما اینکه متد Close_WaitingForm زودتر از Show_WaitingForm اجرا بشه، بستگی داره، همانطور که میدونید متد Initialize_Data یک حجم زیادی از کارها رو انجام میده و این عمل هم وقت گیره، پس چون این عمل وقت گیره، پس متد Close_WaitingForm، دیر صدا زده میشه و میشه شانس زیادی رو قائل شد که متد Show_WaitingForm قبل از متد Close_WaitingForm اجرا بشه... اما اگر متد Initialize_Data کارش رو خیلی سریع انجام بده (یا اصلا کاری انجام نده)، در این صورت متد Close_WaitingForm زودتر از متد Show_WaitingForm اجرا میشه. دلیل اینکه این دو متد میتوانند قبل یا بعد از هم اجرا بشن، دلیلش بر میگرده به اینکه متد Show_WaitingForm و Close_WaitingForm، هر کدوم در یک thread جداگانه (جدا از هم) قرار دارند...

PC2st.ir عزیز ممنون از لطفت ولی زمانی که من شرط null بودن آبجکت frm رو در متد Show_WaitingForm چک بررسی میکنم، دیگه نباید مشکل عدم دسترسی به آبجکت dispose شده داشته باشم.

PC2st
سه شنبه 30 مرداد 1386, 20:28 عصر
PC2st.ir عزیز ممنون از لطفت ولی زمانی که من شرط null بودن آبجکت frm رو در متد Show_WaitingForm چک بررسی میکنم، دیگه نباید مشکل عدم دسترسی به آبجکت dispose شده داشته باشم.
این عملیات به قدری سریع انجام میشه که دقیقا پس از اعمال شرط این حالت رخ میده :لبخند:
اگر شک دارید، متد Hide_WaitingForm رو به شکل زیر تغییر بدید، به احتمال زیاد صدای بوق رو میشنوی و بعد ارور رو میبینی:


public void Hide_WaitingForm()
{
if (frm.InvokeRequired)
{
frm.BeginInvoke(new MethodInvoker(Hide_WaitingForm), null);
}
else
{
frm.Close();
}
Console.Beep();
}

PC2st
سه شنبه 30 مرداد 1386, 20:38 عصر
کدهاتون رو تحت یک نمونه برنامه آپلود کردم، تا شاید دوستان لازم داشتند...

mina1363
سه شنبه 30 مرداد 1386, 20:59 عصر
مرسی ممنونم جناب PC2st.ir

mehdi.mousavi
سه شنبه 30 مرداد 1386, 22:32 عصر
mehdi6755 عزیز ممنون از راهنمایی خوبت، ولی دلیل استفاده من از متد DoEvent() رو با یه مثال بیان میکنم،


سلام.
بسیار خوب. شما هدفتون اینه که اون Label رو Refresh کنید، اما کدی که نوشتید همونطور که گفتم پیچیده تر از این حرفها عمل میکنه. بیایید ببینیم DoEvents چی کار میکنه... هر برنامه ای تو Windows حاوی یه Message Queue هست. هر کاری تو ویندوز باعث ایجاد یک یا چند پیام و گذاشته شدن اون پیام در Message Queue میشه. وقتی شما DoEvents میزنید، باعث میشید کلیه پیامهایی که تا لحظه اجرای DoEvents در صف قرار گرفته شدن، پردازش (Dispatch) بشن. حالا فرض کنید کد زیر رو اجرا می کنید:


private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 1000; i++)
{
this.textBox1.Text = i.ToString();
System.Threading.Thread.Sleep(5000);
Application.DoEvents();
}
}

سپس در کلاس فرمتون، کد زیر رو اضافه کنید:


protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
}

حالا یه Break بذارید تو OnClosing. سپس، برنامه رو تو حالت Debug اجرا کنید. وقتی button1 رو فشار میدید، مقدار موجود در textBox1 به روز میشه. وقتی برنامه در حال اجرا هست، فرم رو Close کنید! همونطور که میبینید، OnClosing فرم صدا زده میشه، اما هنوز تابع button1_Click کارش تموم نشده! بعبارت دیگه، این مساله کاملا شانسی هستش که تو اون لحظه چه پیامی در Message Queue وجود داشته باشه. به همین دلیل باید کاملا مراقب بود که و تحت چه شرایطی میشه این تابع رو صدا زد.

hdv212
سه شنبه 30 مرداد 1386, 23:55 عصر
به همین دلیل باید کاملا مراقب بود که و تحت چه شرایطی میشه این تابع رو صدا زد.
مهدی جان، راه حل شما چیه ؟
در ضمن PC2st.ir عزیز، از زحماتت ممنونم.

mehdi.mousavi
چهارشنبه 31 مرداد 1386, 10:48 صبح
مهدی جان، راه حل شما چیه ؟
در ضمن PC2st.ir عزیز، از زحماتت ممنونم.

سلام.
اما راه حل. کلاس فرمی رو که می خواهیم Refresh بشه رو از IMessageFilter باید درایو کنیم. سپس، این Interface رو باید بدین شکل پیاده سازی کنیم:



private const Int32 WM_PAINT = 0xf;
public Boolean PreFilterMessage(ref Message m)
{
return (m.Msg != WM_PAINT);
}

این به سیستم میگه که کلیه پیامهایی که مخالف WM_PAINT هستن رو فیلتر کنه، در نتیجه اون پیامها از Message Queue دونه به دونه Purge میشن و صفحه همواره Responsive میمونه.

اما هنوز یه مرحله دیگه میمونه، اونم اونجایی که DoEvents رو صدا میزنید. باید یه بار قبلش بیایید و فیلتری که نوشتیم رو به سیستم معرفی کنیم:


Application.AddMessageFilter(this);

و در نهایت، بعد اینکه DoEvents کارش تموم شد،


Application.RemoveMessageFilter(this);

منظورم از this کلاسی هستش که IIMessageFilter رو پیاده سازی میکنه.

اما اگر بر گردم به اصل سوال، من این روش رو نمی پسندم. (البته من کدی رو دیدم که PC2st.ir روی سایت گذاشتن). به نظرم نیازی به یه UI Thread نیست. شما می تونید کاری رو که مدت زمان زیادی طول میکشه رو در یک Worker Thread انجام بدین، و Notification هایی به استفاده کنندگان ارسال کنید تا اونها خودشون رو با شرایط جدید به روز کنن. یکی از روشها، بوجود آوردن Message Bus هستش، یعنی شما کلیه فرامین رو در برنامه بصورت Message هایی در میارید و اونها رو در صفی قرار میدید. سپس Pump ای دارید (در Thread دیگه) که اون Message ها رو Dispatch میکنه و ... البته روشهای زیاد دیگه ای هم وجود داره...

hdv212
چهارشنبه 31 مرداد 1386, 15:08 عصر
mehdi6755 جان منظورت اینه که بعد از پیاده سازی اینترفیس IMessageFilter در فرمم، تابعم رو به شکل زیر اصلاح کنم :

for (int i = 0; i <= 1000; i++)
{
Application.AddMessageFilter(this);
Application.DoEvents();
Application.RemoveMessageFilter(this);
this.toolStripStatusLabel1.Text = i.ToString();
}


یکی از روشها، بوجود آوردن Message Bus هستش، یعنی شما کلیه فرامین رو در برنامه بصورت Message هایی در میارید و اونها رو در صفی قرار میدید. سپس Pump ای دارید (در Thread دیگه) که اون Message ها رو Dispatch میکنه و ... البته روشهای زیاد دیگه ای هم وجود داره...

منظورتو درست متوجه نشدم، میتونی یه نمونه بذاری یا توضیحات بیشتری بدی ؟

mehdi.mousavi
چهارشنبه 31 مرداد 1386, 15:35 عصر
mehdi6755 جان منظورت اینه که بعد از پیاده سازی اینترفیس IMessageFilter در فرمم، تابعم رو به شکل زیر اصلاح کنم :

for (int i = 0; i <= 1000; i++)
{
Application.AddMessageFilter(this);
Application.DoEvents();
Application.RemoveMessageFilter(this);
this.toolStripStatusLabel1.Text = i.ToString();
}

منظورتو درست متوجه نشدم، میتونی یه نمونه بذاری یا توضیحات بیشتری بدی ؟


سلام.
منظورم اینه:


Application.AddMessageFilter(this);
for (int i = 0; i <= 1000; i++)
{
this.toolStripStatusLabel1.Text = i.ToString();
Application.DoEvents();
}
Application.RemoveMessageFilter(this);


تو این سایت (http://geekswithblogs.net/davidl/archive/2004/05/13/4859.aspx) چند نمونه از Message Bus Implementation ها داده شده. البته باید بیشتر بگردم، شاید مقاله ای پیدا کردم که کامل توضیح بده.