PDA

View Full Version : سوال: نحوه استفاده از BackgroundWorker برای همزمان سازی



Saeed_m_Farid
سه شنبه 08 دی 1388, 18:56 عصر
سلام
دوستان عزیز سی شارپ کار گل، خسته نباشید.
راستش من زیاد تو پروژه های Win32 Form C#‎‎‎‎‎‎.Net کار نکردم، اکثراً اگه با دات نت هم کار داشتم با وب سرویس ها کار داشتم تا UI و ...
بگذریم، می خواستم ببینم چطوری میشه برای Synchronize استفاده کردن (یا بقول دات نت ها Thread-safe کردن) عناصر روی فرم از BackgroundWorker استفاده کرد؟ تو اینجا (http://msdn.microsoft.com/en-us/library/ms171728.aspx) یه توضیحاتی در این زمینه داده ولی زیاد با کار من همخوانی نداره یا من درست متوجه نمیشم؛ دو تا راه حل اونجا پیشنهاد شده که یکیش توی خود Thread فرعی داره یه تابع Callback استفاده میکنه، که من اینکار رو نمیخوام بکنم و اون یکی هم از BackgroundWorker (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx) استفاده کرده و این بنظرم خیلی بهتر اومد ولی نتونستم implement اش بکنم؛ برای اینکه بهتر منظورم رو بگیرید (با اینکه ممکنه تکراری باشه ولی من پاسخ مناسب پیدا نکردم) : باید بگم من برای گرفتن محتویات چندین جدول و نوشتن همزمان log مراحل دچار مشکل شدم! یعنی کاری که خیلی راحت همیشه تو دلفی (بخاطر عادت به سینتکس اش) انجام میدم رو اینجا نمی تونم انجام بدم، کد مورد نظر (نمونه هست نه اصلی) می تونه به شکل زیر باشه که من از اساتید محترم میخوام قسمت کامنت شده رو دقت کنند، اون خط قرمز جایی هست که به مشکل برخوردم و نتونستم ادامه بدم :

// lstboxLogs = Is a ListBox to log events.
private void addLog(object log)
{
string msg = string.Format("{0:F} > {1}", DateTime.Now, log);
lstboxLogs.Items.Add(msg);
}

// cbTablesName = A ComboBox contain Table(s) name
// GetHotBillTableContents = A WebMethod return Dataset by TableName
// dataGV = A DataGridView for show Dataset contains.
private void update_sql_tables()
{
for (int idx = 0; idx < cbTablesName.Items.Count - 1; idx++)
{
addLog("Fetch Table <" + cbTablesName.Items[idx].ToString() + "> Contents ...");
DataSet setting_ds = hotbill.GetHotBillTableContents(cbTablesName.Items[idx].ToString());
addLog(string.Format("Table <{0}> have [{1}] records.",
cbTablesName.Items[idx].ToString(),
setting_ds.Tables[0].Rows.Count));
if (setting_ds.Tables.Count > 0)
{
dataGV.DataSource = setting_ds.Tables[0];
dataGV.AutoResizeColumns();
Refresh();
Thread.Sleep(1000); // replace with a retardation procedur ...
}
}
}

private void btnUpdateSettings_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Are you sure to update all tables?", "Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1) == DialogResult.Yes)
{
btnGetTables_Click(sender, e);

addLog(string.Format("{0} <{1}> {2}", "Start update", cbTablesName.Items.Count, "Tables"));
/*
Thread th = new Thread(new ThreadStart(update_sql_tables));
th.IsBackground = true;
th.Start();
*/
update_sql_tables(); // I want do this function in thread, and also log steps ...
}
}

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

ممنون.

Saeed_m_Farid
سه شنبه 08 دی 1388, 19:21 عصر
متاسفانه تو بلاک تگ درست عمل نمی کنه و بالطبع خط قرمزی هم تو کد بالا موجود نبود! (قابل توجه vBulletin® Version 3.8.0 :لبخند:)
خلاصه اش اینکه من هرجا از عناصر روی فرم تو تابع update_sql_tables استفاده کردم، مشکل مزبور [1] هست و مثلاً lstboxLogs یاdataGV رو صدا کردم به مشکل برخوردم و معلوم بود که به مشکل برمیخورم، چون از Thread ای دارم اونها رو فراخوانی می کنم که مالکِشون نیست؛ ولی راه پیاده سازی برای فراخوانی Thread-safe عناصر روی فرم پیدا نکردم!
ممنون میشم اگه با توضیحات راهنمایی کنید ...

[COLOR=Blue][1] مشکل مزیور =
[source="c-sharp"]System.InvalidOperationException was unhandled
Message="Cross-thread operation not valid: Control 'lstboxLogs' accessed from a thread other than the thread it was created on."
Source="System.Windows.Forms"
StackTrace:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SendMessage(Int32 msg, Int32 wparam, String lparam)
at System.Windows.Forms.ListBox.NativeAdd(Object item)
at System.Windows.Forms.ListBox.ObjectCollection.AddI nternal(Object item)
at System.Windows.Forms.ListBox.ObjectCollection.Add( Object item)
at TestHotBilling.frmHotBilling.addLog(Object log) in ..\..\..\..\frmTesterMain.cs:line 152
at TestHotBilling.frmHotBilling.update_sql_tables() in ..\..\..\..\frmTesterMain.cs:line 162
at System.Threading.ThreadHelper.ThreadStart_Context( Object state)
at System.Threading.ExecutionContext.Run(ExecutionCon text executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:

Saeed_m_Farid
چهارشنبه 09 دی 1388, 08:53 صبح
سلام
میدونم که بازم کسی توجه نمی کنه، ولی بازم وظیفه خودم دونستم تا جایی که خودم پیش رفتم اینجا بذارم تا دوستانی که تا این حد مشکل شون مرتفع میشه، استفاده کنند.

تو کد زیر از روش delegate و Callback استفاده کردم و با اینکه هیچ سوادی تو #C ندارم تونستم اینطوری (با استفاده از msdn و بر پایه سواد دلفی!) جواب بگیرم. دوستان اگه مشکلی تو کد من می بینن، خیلی خوشحال میشم که رفع اشکال کنند تا دوستان دیگه هم بعداً (با دیدن این کد) به اشتباه نیفتن ...



// This delegate enables asynchronous calls for adding
// the text property into a ListBox control.
delegate void AddTextCallback(string text);

// This method is passed in to the AddTextCallBack delegate
// to add the Text property of lstboxLogs.
private void AddText(string text)
{
this.lstboxLogs.Items.Add(text);
}

// lstboxLogs = Is a ListBox to log events.
private void addLog(object log)
{
string msg = string.Format("{0:F} > {1}", DateTime.Now, log);

// Check if this method is running on a different thread
// than the thread that created the control.
if (this.lstboxLogs.InvokeRequired)
{
// It's on a different thread, so use Invoke.
AddTextCallback d = new AddTextCallback(AddText);
this.Invoke
(d, new object[] { msg + " (From Thread)" });
}
else
{
// It's on the same thread, no need for Invoke
AddText(msg);
}
}

// This delegate enables asynchronous calls for setting
// the Dataset property to a DataGridView control.
delegate void Update_dataGVCallback(DataTable table);

// This method is passed in to the Update_dataGVCallback delegate
// to set the DataTable property to dataGV.
private void Update_dataGV(DataTable table)
{
if (this.dataGV.InvokeRequired)
{
Update_dataGVCallback d = new Update_dataGVCallback(Update_dataGV);
this.Invoke
(d, new object[] {table});
}
else
{
this.dataGV.DataSource = table;
this.dataGV.AutoResizeColumns();
this.Refresh();
}
}

// cbTablesName = A ComboBox contain Table(s) name
// GetHotBillTableContents = A WebMethod return Dataset by TableName
// dataGV = A DataGridView for show Dataset contains.
private void update_sql_tables()
{
for (int idx = 0; idx < cbTablesName.Items.Count - 1; idx++)
{
addLog("Fetch Table <" + cbTablesName.Items[idx].ToString() + "> Contents ...");
DataSet setting_ds = hotbill.GetHotBillTableContents(cbTablesName.Items[idx].ToString());
addLog(string.Format("Table <{0}> have [{1}] records.",
cbTablesName.Items[idx].ToString(),
setting_ds.Tables[0].Rows.Count));
if (setting_ds.Tables.Count > 0)
{
Update_dataGV(setting_ds.Tables[0]);
Thread.Sleep(1000); // replace with a retardation procedur ...
}
}
}

private void btnUpdateSettings_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Are you sure to update all tables?", "Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1) == DialogResult.Yes)
{
btnGetTables_Click(sender, e);

addLog(string.Format("{0} <{1}> {2}", "Start update", cbTablesName.Items.Count, "Tables"));
Thread th = new Thread(new ThreadStart(update_sql_tables));
th.IsBackground = true;
th.Start();
//update_sql_tables(); // I want do this function in thread, and also log steps ...
}
}


فعلاً با همین کار من راه افتاد ولی چیزی که من میخواستم استفاده از BackgroundWorker (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx) برای رفع مشکل مطرح شده بود که اساتید هر موقع وقت کردند، ممنون میشم که یه راهنمایی بفرمایند ...

vcldeveloper
چهارشنبه 09 دی 1388, 13:13 عصر
استفاده از Control.Invoke تقریبا مشابه استفاده از TThread.Queue در دلفی هست. یک مدخل جدید در صف Thread مالک کنترل ایجاد میکنه، و به همراه آن یک method pointer هم ارسال میکنه تا اون Thread بدونه چه متدی رو باید اجرا کنه. البته TThread.Queue فقط با Thread اصلی برنامه کار میکنه، چون در VCL فقط Thread اصلی مالک اشیاء ویژوال هست، ولی در دات نت Control.Invoke دنبال Thread مالک کنترل میگرده، و بعد از پیدا کردن آن، پیام را بهش ارسال میکنه.

استفاده از BackgroundWorker از نظر Performance نباید تفاوت خاصی با Control.Invoke داشته باشه، غیر از اینکه در Control.Invoke یک مرحله جستجوی Thread هم وجود داره. البته من عملا تست نکردم که تفاوت Performanceشان با هم چقدر هست.
در BackgroundWorker تعامل شما با Thread اصلی هست، و فقط از طریق دو رویداد RunWorkerCompleted و ProgressChanged انجام میشه، پس کدهای شما مثل Add_log یا Update_dataGV باید در یکی از این دو رویداد فراخوانی بشند، البته در این صورت دیگه نیازی به بررسی IsInvokeRequired نیست.
در دلفی بطور استاندارد معادلی برای BackgroundWorker وجود نداره، ولی میشه مشابه آن را نوشت، یا از کتابخانه های رایگان که قابلیت مشابهی ارائه می کنند، استفاده کرد، مثل OmniThread.

Saeed_m_Farid
چهارشنبه 09 دی 1388, 16:14 عصر
...
استفاده از BackgroundWorker از نظر Performance نباید تفاوت خاصی با Control.Invoke داشته باشه، غیر از اینکه در Control.Invoke یک مرحله جستجوی Thread هم وجود داره. البته من عملا تست نکردم که تفاوت Performanceشان با هم چقدر هست.
...
در BackgroundWorker تعامل شما با Thread اصلی هست، و فقط از طریق دو رویداد RunWorkerCompleted و ProgressChanged انجام میشه، پس کدهای شما مثل Add_log یا Update_dataGV باید در یکی از این دو رویداد فراخوانی بشند، البته در این صورت دیگه نیازی به بررسی IsInvokeRequired نیست.

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

گذشته از شوخی؛ پس بنظر شما کد بالا عملاً مشکلی نداره؟ حالا Performance اش بماند، این کد Thread-safeهست دیگه ان شا... ؟
راستش یکمی ناجور بنظر میرسه که داخل خود تابعی که قرار هست Asynchron فراخوانی بشه، یه CallBack تنظیم (Set) کنیم! اونهم با این سینتکس عجیب که new object رو Invoke میکنه و ...

بازم ممنون.

vcldeveloper
چهارشنبه 09 دی 1388, 17:15 عصر
گذشته از شوخی؛ پس بنظر شما کد بالا عملاً مشکلی نداره؟ حالا Performance اش بماند، این کد Thread-safeهست دیگه ان شا... ؟من از جزئیاتش اطلاعی ندارم، Visual Studio هم الان ندارم که بخوام تست کنم، ولی منطق کار درست هست، و کدهای مربوط به تغییر UI از سایر کدها جدا شدند، و توسط Threadهای مالک اون اشیاء ویژوال تغییر می کنند.


راستش یکمی ناجور بنظر میرسه که داخل خود تابعی که قرار هست Asynchron فراخوانی بشه، یه CallBack تنظیم (Set) کنیم! اونهم با این سینتکس عجیب که new object رو Invoke میکنه و ...خب دیگه، هر زبانی شکل و شمایل خودش رو داره.

bashiry
پنج شنبه 10 دی 1388, 10:11 صبح
سلام
يا BackgroundWorker كار مي كردم كه در هنگام اجرا متوجه شدم كه ThreadSafe نيست. دست به دامان MSDN شدم كه راه حل خوبي براش ارائه كرده:


عنوان مطلب :


How to: Make Thread-Safe Calls to Windows Forms Controls


لينك آموزش داخل msdn2005 :


ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm



اين هم سورس كد:


Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms

Public Class form3
Inherits Form

' This delegate enables asynchronous calls for setting
' the text property on a TextBox control.
Delegate Sub SetTextCallback(ByVal [text] As String)

' This thread is used to demonstrate both thread-safe and
' unsafe ways to call a Windows Forms control.
Private demoThread As Thread = Nothing

' This BackgroundWorker is used to demonstrate the
' preferred way of performing asynchronous operations.
Private WithEvents backgroundWorker1 As BackgroundWorker

Private textBox1 As TextBox
Private WithEvents setTextUnsafeBtn As Button
Private WithEvents setTextSafeBtn As Button
Private WithEvents setTextBackgroundWorkerBtn As Button

' Private components As System.ComponentModel.IContainer = Nothing


Public Sub New()
InitializeComponent()
End Sub


Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing AndAlso Not (components Is Nothing) Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub


' This event handler creates a thread that calls a
' Windows Forms control in an unsafe way.
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click

Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))

Me.demoThread.Start()
End Sub


' This method is executed on the worker thread and makes
' an unsafe call on the TextBox control.
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
End Sub

' This event handler creates a thread that calls a
' Windows Forms control in a thread-safe way.
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextSafeBtn.Click

Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))

Me.demoThread.Start()
End Sub


' This method is executed on the worker thread and makes
' a thread-safe call on the TextBox control.
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub

' This method demonstrates a pattern for making thread-safe
' calls on a Windows Forms control.
'
' If the calling thread is different from the thread that
' created the TextBox control, this method creates a
' SetTextCallback and calls itself asynchronously using the
' Invoke method.
'
' If the calling thread is the same as the thread that created
' the TextBox control, the Text property is set directly.

Private Sub SetText(ByVal [text] As String)

' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub

' This event handler starts the form's
' BackgroundWorker by calling RunWorkerAsync.
'
' The Text property of the TextBox control is set
' when the BackgroundWorker raises the RunWorkerCompleted
' event.
Private Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub


' This event handler sets the Text property of the TextBox
' control. It is called on the thread that created the
' TextBox control, so the call is thread-safe.
'
' BackgroundWorker is the preferred way to perform asynchronous
' operations.
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
End Sub

#Region "Windows Form Designer generated code"


Private Sub InitializeComponent()
Me.textBox1 = New System.Windows.Forms.TextBox()
Me.setTextUnsafeBtn = New System.Windows.Forms.Button()
Me.setTextSafeBtn = New System.Windows.Forms.Button()
Me.setTextBackgroundWorkerBtn = New System.Windows.Forms.Button()
Me.backgroundWorker1 = New System.ComponentModel.BackgroundWorker()
Me.SuspendLayout()
'
' textBox1
'
Me.textBox1.Location = New System.Drawing.Point(12, 12)
Me.textBox1.Name = "textBox1"
Me.textBox1.Size = New System.Drawing.Size(240, 20)
Me.textBox1.TabIndex = 0
'
' setTextUnsafeBtn
'
Me.setTextUnsafeBtn.Location = New System.Drawing.Point(15, 55)
Me.setTextUnsafeBtn.Name = "setTextUnsafeBtn"
Me.setTextUnsafeBtn.TabIndex = 1
Me.setTextUnsafeBtn.Text = "Unsafe Call"
'
' setTextSafeBtn
'
Me.setTextSafeBtn.Location = New System.Drawing.Point(96, 55)
Me.setTextSafeBtn.Name = "setTextSafeBtn"
Me.setTextSafeBtn.TabIndex = 2
Me.setTextSafeBtn.Text = "Safe Call"
'
' setTextBackgroundWorkerBtn
'
Me.setTextBackgroundWorkerBtn.Location = New System.Drawing.Point(177, 55)
Me.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"
Me.setTextBackgroundWorkerBtn.TabIndex = 3
Me.setTextBackgroundWorkerBtn.Text = "Safe BW Call"
'
' backgroundWorker1
'
'
' form3
'
Me.ClientSize = New System.Drawing.Size(268, 96)
Me.Controls.Add(setTextBackgroundWorkerBtn)
Me.Controls.Add(setTextSafeBtn)
Me.Controls.Add(setTextUnsafeBtn)
Me.Controls.Add(textBox1)
Me.Name = "form3"
Me.Text = "form3"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub 'InitializeComponent

#End Region

<STAThread()> _
Shared Sub Main()
Application.EnableVisualStyles()
Application.Run(New form3())
End Sub
End Class

Saeed_m_Farid
شنبه 12 دی 1388, 14:59 عصر
سلام
يا BackgroundWorker كار مي كردم كه در هنگام اجرا متوجه شدم كه ThreadSafe نيست. دست به دامان MSDN شدم كه راه حل خوبي براش ارائه كرده ...
سلام
دست شما درد نکنه ولی این که همون لینکی هست که من تو پست اول گذاشتم! شما فقط کد VB اش رو کپی کردید که متاسفانه به درد من نمیخوره ...

... تو اینجا (http://msdn.microsoft.com/en-us/library/ms171728.aspx) یه توضیحاتی در این زمینه داده ولی زیاد با کار من همخوانی نداره ...
مشکل من از طریق Invoke و Callback حل شد (تو پست سوم همین تاپیک (http://barnamenevis.org/forum/showpost.php?p=877799&postcount=3)) ولی مشتاقم تا دوستانی که راه حل عملی برای پیاده سازی این کد با BackgroundWorker دارند، ارائه کنند تا نحوه کاربرد اون رو هم یاد بگیریم؛ یعنی من درون تابعی که به Thread معرفی می کنم، بتونم با BackgroundWorker مثلاً دیتاگرید رو Asynchron پر کنم یا addlog رو فراخوانی کنم و ...

یه چیزی شبیه این، ولی برای کد ذکر شده :

// This method starts BackgroundWorker by calling
// RunWorkerAsync. The Text property of the TextBox control
// is set by a method running in the main thread
// when BackgroundWorker raises the RunWorkerCompleted event.
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted +=
new System.ComponentModel.RunWorkerCompletedEventHandl er(this.backgroundWorker1_RunWorkerCompleted);
this.backgroundWorker1.RunWorkerAsync();

// Continue in the main thread.
textBox1.Text = "Written by the main thread.";
}

// This method does the work you want done in the background.
void backgroundWorker1_DoWork (object sender, DoWorkEventArgs e)
{
// Wait two seconds to simulate some background work
// being done.
Thread.Sleep(2000);

// You could use the same technique as in the
// ThreadProcSafe method to set textBox1.Text here, but
// the preferred method is to do it from the Completed
// event handler which runs in the same thread as the one
// that created the control.
}

// This method is called by BackgroundWorker's
// RunWorkerCompleted event. Because it runs in the
// main thread, it can safely set textBox1.Text.
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.textBox1.Text =
"Written by the main thread after the background thread completed.";
}


بازم ممنون.

mehdi.mousavi
شنبه 12 دی 1388, 15:41 عصر
می خواستم ببینم چطوری میشه برای Synchronize استفاده کردن (یا بقول دات نت ها Thread-safe کردن)

سلام.
واژه Thread-Safe ربطی به .NET Framework نداره و قبل از اونهم توسط برنامه نویسان C/C++ استفاده میشده (اگر چه اونها Synchronization رو بیشتر استفاده میکنن)، بنابراین این بخش از گفته شما که "بقول دات نت کارها..." چندان صحیح نیست.

اما در مورد BackgroundWorker... ببینید. فرض کنیم شما میخواهید کاری رو که مدت زمان زیادی بطول می انجامه رو در یک Worker Thread انجام بدید. برای اینکار، دستورالعملهای مزبور رو در Thread دیگه ای انجام میدید و Notification های لازم (در صورت نیاز) رو به UI Thread (یا همون Main Thread) ارسال میکنید. BackgroundWorker اینکارو خیلی ساده کرده. شما میتونید توی DoWork کار زمانبر خودتون رو انجام بدید... و هر از گاهی (بسته به نیاز) Main-Thread رو از روند کار Worker Thread مطلع کنید. مثلا:


private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Our lengthy operation
for (int i = 0; i < 100; i++)
{
backgroundWorker1.ReportProgress(i + 1, "whatever state");
}
}


بعنوان نمونه من توی لوپ فوق دارم یک کار زمانبر رو انجام میدم. و در مراحلی (اینجا بازای هر بار تغییر i) دارم روند اجرای Worker Thread رو به UI Thread اطلاع میدم. اما حواستون باشه که برای اجرای کد فوق، حتما باید دستور زیر قبلش از راه اندازی Thread اجرا بشه:

backgroundWorker1.WorkerReportsProgress = true;

حالا، هنگامیکه توی UI Thread خودم، از پیشرفت کار Worker Thread مطلع شدم، میتونم کارم با UI رو انجام بدم:


private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string userState = e.UserState as string;
Debug.Assert(userState == "whatever state");
}


اینجا، میتونم براحتی برم و تغییرات لازم رو روی UI اعمال کنم. ضمن اینکه، درصد پیشرفت کار و UserState دلخواهم هم از Worker Thread بدست UI-Thread رسیده که در مثال بالا، string ای بود که ...

برای Cancel کردن Worker Thread هم میتونید از CancelAsync استفاده کنید. فقط حواستون باشه که خودتون باید توی Interval های دلخواهتون CancellationPending رو چک کنید تا اگر true بود، کار Worker Thread رو خاتمه داده و از تابع DoWork خارج بشید. (این کار مایکروسافت واقعا احمقانه بوده، جای اینکه Event رو بشه Signal کرد و ... اومده از یک متغیر boolean برای cancel کردن کار استفاده میکنه. که خوب، اگر برنامه نویس حواسش نباشه، کد نهایی cpu-usage رو میتونه بالا ببره).

موفق باشید.

Saeed_m_Farid
شنبه 12 دی 1388, 17:58 عصر
سلام.
واژه Thread-Safe ربطي به .NET Framework نداره و قبل از اونهم توسط برنامه نويسان C/C++‎‎‎‎‎‎‎ استفاده ميشده (اگر چه اونها Synchronization رو بيشتر استفاده ميکنن)، بنابراين اين بخش از گفته شما که "بقول دات نت کارها..." چندان صحيح نيست.

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


برای Cancel کردن Worker Thread هم میتونید از CancelAsync استفاده کنید. فقط حواستون باشه که خودتون باید توی Interval های دلخواهتون CancellationPending رو چک کنید تا اگر true بود، کار Worker Thread رو خاتمه داده و از تابع DoWork خارج بشید. (این کار مایکروسافت واقعا احمقانه بوده، جای اینکه Event رو بشه Signal کرد و ... اومده از یک متغیر boolean برای cancel کردن کار استفاده میکنه. که خوب، اگر برنامه نویس حواسش نباشه، کد نهایی cpu-usage رو میتونه بالا ببره).

بیخیال Cancle، آقا من تو working اش مشکل دارم!
از راهنمایی هاتون خیلی مرسی، دو قرونی افتاد و با توضیحات شما و راهنمایی های MSDN کد من شد بصورت زیر :
یعنی بجای CallBack از BackgroundWorker استفاده کردم.

private void bgwTablUpdater_DoWork(object sender, DoWorkEventArgs e)
{
bool bAllTransfered = true;
if ((MessageBox.Show("Are you sure to update all tables?", "Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1) == DialogResult.Yes) &&
(lstboxTables.Items.Count > 0))
{
int idx, iPercent = 0;
for (idx = 0; idx < lstboxTables.Items.Count; idx++)
{
try
{
Select_TableItem(idx);
iPercent = (idx * 100) / lstboxTables.Items.Count;
DataSet setting_ds = hotbill.GetHotBillTableContents(lstboxTables.Items[idx].ToString());
bgwTablUpdater.ReportProgress(iPercent, string.Format("[{0}] Table <{1}> have [{2}] records.",
idx + 1,
lstboxTables.Items[idx].ToString(),
setting_ds.Tables[0].Rows.Count));
if (setting_ds.Tables.Count > 0)
{
Update_dataGV(setting_ds.Tables[0]);
int tdx = sqlTablesList.IndexOf(lstboxTables.Items[idx].ToString());
if (!transferTable(lstboxTables.Items[idx].ToString(),
setting_ds.Tables[0],
(tdx >= 0)))
break;
}
}
catch (Exception ex)
{
bgwTablUpdater.ReportProgress(iPercent, "[ER]: "+ex.Message);
bAllTransfered = false;
}
}
if (bAllTransfered)
bgwTablUpdater.ReportProgress(100, "Process Completed Successfull.");
else
bgwTablUpdater.ReportProgress(-1, "[WR] Process Not Completed.");
}
else
MessageBox.Show("There is not any Update Table existed in list!"
+ "\nTry \"Get HotBill Tables\" first", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

//=================

private void bgwTablUpdater_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
AddText(string.Format("[{0}%] : {1}",
(e.ProgressPercentage).ToString() +
e.UserState as string));
}

//=================

private void bgwTablUpdater_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
AddText("Written by the main thread after the background thread completed.");
}

//=================

private void btnUpdateSettings_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Are you sure to update all tables?", "Warning",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1) == DialogResult.Yes)
{
btnUpdateSettings.Enabled = false;
bgwTablUpdater.DoWork += new DoWorkEventHandler(bgwTablUpdater_DoWork);
bgwTablUpdater.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwTablUpdater_RunW orkerCompleted);
bgwTablUpdater.ProgressChanged += new ProgressChangedEventHandler(bgwTablUpdater_Progres sChanged);
bgwTablUpdater.WorkerReportsProgress = true;
bgwTablUpdater.RunWorkerAsync();
}
}


یه سوال : نام توابعی که از ترکیب پسوندها (DoWork و RunWorkerCompleted و ...) و نام تابع اصلی درست میشن و Event Handler ها رو درست می کنند، میشه عوض بشن؟ یعنی نام تابع من یه چیز دیگه باشه : مثلاً bgwTablUpdater_DoWork بشه DoWorkTablUpdater، فکر نکنم نشه! چون من دارم دستی Event Handler رو اختصاص میدم، درسته؟

مشکل : یک استثناء یا بهتر اگر گویم : اعتراض (بیگانگان Exception گویند) خفن دریافت می کنیم، بدین سان : Exception has been thrown by the target of an invocation.
و جزئیات اش مشتمل بر موارد ذیل می گردد:


System.Reflection.TargetInvocationException was unhandled
Message="Exception has been thrown by the target of an invocation."
Source="mscorlib"
StackTrace:
at System.RuntimeMethodHandle._InvokeMethodFast(Objec t target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Windows.Forms.Control.InvokeMarshaledCallba ckDo(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallba ckHelper(Object obj)
at System.Threading.ExecutionContext.runTryCode(Objec t userData)
at System.Runtime.CompilerServices.RuntimeHelpers.Exe cuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(Exec utionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionCon text executionContext, ContextCallback callback, Object state)
at System.Windows.Forms.Control.InvokeMarshaledCallba ck(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallba cks()
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.O nMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.W ndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallba ck(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchM essageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager. System.Windows.Forms.UnsafeNativeMethods.IMsoCompo nentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.Run MessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.Run MessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at TestHotBilling.Program.Main() in ..\..\Program.cs:line 17
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.Run UsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context( Object state)
at System.Threading.ExecutionContext.Run(ExecutionCon text executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: System.FormatException
Message="Index (zero based) must be greater than or equal to zero and less than the size of the argument list."
Source="mscorlib"
StackTrace:
at System.Text.StringBuilder.AppendFormat(IFormatProv ider provider, String format, Object[] args)
at System.String.Format(IFormatProvider provider, String format, Object[] args)
at System.String.Format(String format, Object arg0)
at TestHotBilling.frmHotBilling.bgwTablUpdater_Progre ssChanged(Object sender, ProgressChangedEventArgs e) in F:\Projects\ElkaHtoBillingService\TestThread\frmTe sterMain.cs:line 602
at System.ComponentModel.BackgroundWorker.OnProgressC hanged(ProgressChangedEventArgs e)
at System.ComponentModel.BackgroundWorker.ProgressRep orter(Object arg)
InnerException:


احتمال بر ذخیره نمودن رویدادها در فایل میرود، شما چنین نمی پندارید؟ یا شاید هم سبب این باشد که ما تابع Update_dataGV را استفاده کرده (و آن نیز به نوبه خود از "رویه طلب نمودن" و به زبان باختر : InvokeMethod مستفیذ گردیده است) و سبب بروز مشکلاتی از این قسم می گردد ...
(امیدوارم با این جمله بندی دیگه اشکال نحوی به من نگیرید :لبخندساده: : شوخی بود ها... ناراحت نشین!)

بازم از توجهتون ممنون.

mehdi.mousavi
شنبه 12 دی 1388, 18:25 عصر
سلام، مثل همیشه آقا مهدی خشن! حالا چندان صحیح نباشه، چی میشه مگه؟ میخواستم زود با بچه ها پسرخاله بشیم، تحویلمون بگیرن ...

سلام.
خشونت کدومه؟ :لبخندساده:


یه سوال : نام توابعی که از ترکیب پسوندها (DoWork و RunWorkerCompleted و ...) و نام تابع اصلی درست میشن و Event Handler ها رو درست می کنند، میشه عوض بشن؟ یعنی نام تابع من یه چیز دیگه باشه : مثلاً bgwTablUpdater_DoWork بشه DoWorkTablUpdater، فکر نکنم نشه! چون من دارم دستی Event Handler رو اختصاص میدم، درسته؟

البته که درسته. شما میتونید هر اسمی رو که مایل بودید به تابع خودتون اختصاص بدید. حتی میتونید از anonymous delegate ها استفاده کنید (که البته اینکارو نکنید).


مشکل : یک استثناء یا بهتر اگر گویم : اعتراض (بیگانگان Exception گویند) خفن دریافت می کنیم، بدین سان : Exception has been thrown by the target of an invocation.
و جزئیات اش مشتمل بر موارد ذیل می گردد:

خوب. قبل از اینکه به این مساله بپردازم، اجاره بدید ابتدا یه چیز دیگه بگم. اونم اینکه قرار شد توی Worker Thread کاری با UI نداشته باشید. شما نباید توی DoWork پنجره باز کنید و ... منظورم MessageBox هستش... این کارو قبل از Start کردن Thread انجام بدید. مساله دوم گرفتن همه Exception ها توسط Catch(Exception ex) هستش. این کد، بیشتر از اونی که فکر میکنید خطرناکه، چون SEH های Windows رو هم میتونه بگیره!!! (توضیحش رو میتونید در یکی از مقالات شماره های اخیر مجله MSDN پیدا کنید).

اما در مورد Exception ای میگیرید، خوب برای فهمیدن این موضوع، میتونید توی Exception اتون یک Breakpoint بذارید، هر وقت اجرا اونجا پرید، Call Stack رو نگاه کنید و سریع السیر به ایراد پی ببرید. به همین سادگی...

ضمنا، لازم نیست با هر بار Click شدن Button، شما برای گرفتن Event مورد نظر، Subscribe کنید. فقط یک بار، اول کار (هنگام Load فرم، یا در CTOR) اینکارو انجام بدید... در نهایت، مطمئن بشید که هر کاری توی DoWork میکنید، ربطی به UI نداشته باشه....

موفق باشید.

vcldeveloper
یک شنبه 13 دی 1388, 03:57 صبح
چون SEH های Windows رو هم میتونه بگیره!!! (توضیحش رو میتونید در یکی از مقالات شماره های اخیر مجله MSDN پیدا کنید).میشه لینکش رو بدید؟ تا جایی که من اطلاع دارم، Exceptionهای خاصی مثل Guard page exception بطور خودکار توسط مکانیزم SEH ارائه شده توسط runtime بدون اینکه catch بشند، به Execption handler سیستم عامل می رسند، تا کدهایی که هیچ شرطی برای Exceptionهای Catch شده نمیگذارند، موجب خرابکاری نشند.
البته من از مکانیزم داخلی SEH در Runtime دات نت اطلاع دقیقی ندارم، ولی معمولا این کاری هست که انجام میشه؛ runtime یک زبان برنامه نویسی مکانیزمی برای SEH ارائه میکنه که معمولا یک wrapper به دور مکانیزم SEH در Win32 هست، و این مکانیزم داخلی SEH برخی Exceptionهای خاص رو که نباید توسط کدهای کاربر Handle بشند را فیلتر می کنه، تا به کد کاربر نرسه.

حالا اگر لینک اون مقاله ایی که بهش اشاره کردید رو قرار بدید، احتمالا من بهتر متوجه منظور شما و چگونگی عملکر SEH در Runtime دات نت بشم.

با تشکر

mehdi.mousavi
یک شنبه 13 دی 1388, 11:22 صبح
حالا اگر لینک اون مقاله ایی که بهش اشاره کردید رو قرار بدید، احتمالا من بهتر متوجه منظور شما و چگونگی عملکر SEH در Runtime دات نت بشم. با تشکر

سلام.
لینک به مقاله مزبور: Handling Corrupted State Exception (http://msdn.microsoft.com/en-us/magazine/dd419661.aspx)
نوشته Andrew Pardoe

موفق باشید.

vcldeveloper
یک شنبه 13 دی 1388, 16:54 عصر
لینک به مقاله مزبور: Handling Corrupted State Exception (http://msdn.microsoft.com/en-us/magazine/dd419661.aspx)
نوشته Andrew Pardoe
مرسی، مقاله جالبی بود. این قابلیت جدیدی هم که در نسخه 4.0 دات نت برای کنترل Exceptionهای External گذاشتن، چیز جالبی هست. البته من ندیدم چندان درباره اش اطلاع رسانی بشه (من خودم هم زیاد اخبار دات نت را دنبال نمی کنم)، ولی در این حالت باید موجب سردرگمی های زیادی بشه! مثلا کاربری که یک پروژه جدید در VS 2010 ایجاد میکنه، و به خیال اینکه کدی که در بلوک finally مینویسه حتما اجرا خواهد شد، در صورت دریافت یک External Exception کاملا گیج میشه. البته خطاهایی مثل AV اونقدر که در Native Code معمول هستند، در Managed Code پیش نمیان، و شاید به همین خاطر هم چندان درباره اش اطلاع رسانی نشده؛ یا شاید هم شده، و من از اخبار دات نت زیاد مطلع نیستم.

البته فکر کنم در دات نت هم برای External Exceptionها یک کلاس پایه واحد وجود داشته باشه، و برنامه نویس بتونه با بررسی کلاس Exception رخ داده در بلوک Except، در صورتی که Exception از این کلاس پایه مشتق شده باشه، از برنامه خارج بشه. در دلفی کلاس EExternal برای همین منظور وجود داره، و میشه کدی مثل این نوشت:

try
{Yout protected code}
except
on e: EExternal do
Application.Terminate
else
begin
{Handle exceptions which are raised within the current thread }
end;
end;

در مورد کد آقای Saeed_m_Farid (http://www.barnamenevis.org/forum/member.php?u=41415) ایشون میتونند نام کلاس Exception رو هم به عنوان User Data به ReportProgress ارسال کنند، و اگر کلاس ارسالی مربوط به یکی از External Exceptionها بود، Thread اصلی اجرای Worker Thread رو متوقف کنه، و اجرای Process رو خاتمه بده.

dr_jacky_2005
یک شنبه 28 شهریور 1389, 14:02 عصر
شما میتونید توی DoWork کار زمانبر خودتون رو انجام بدید... و هر از گاهی (بسته به نیاز) Main-Thread رو از روند کار Worker Thread مطلع کنید.

آقا مشکل من اینه که یک تابع دارم که کار پر کردن گریدم رو انچام میده.و این تابع توو لود باید باشه.
یعنی نه اینکه حالا این تابع حتما باید توو فرم باشه ها!منظورم اینه که حتما باید توو لود فرم گریدم پر بشه.

مشکل اینجاست که توو DoWork چی بنویسم!؟

توضیحات دیگم اینجاس:
http://www.barnamenevis.org/forum/showthread.php?p=1102662