How to speed up a cross thread call?

  • Thread starter Thread starter Kueishiong Tu
  • Start date Start date
K

Kueishiong Tu

I have a window form application. I create a worker
thread to make a web request call to retireve data from
the web. When the work is done, I make a marshaled call
to raise an event in the main thread (thanks to William
Ryan, Christine Nguyen, John Saunders, Wiktor Zychla help,
particularly John Saunders). The main thread then
populate the data on a listview on the form. However, I
found the data displaying is very slow compared to the
situation if I wait for the worker thread to finish
through a thread->Join call. Even worse the forecolor and
backcolor are not displayed correctly. What happens here?
Is it becuase the worker thread is still hanging around
so it slows down the main thread? If so, how to get rid
of the worker thread after the cross thread event is
raised?
 
Hi Kueishiong,
I missed the thread where the solution was build and I don't know how you
notify the main thread and marshal the data. Anyway, I suppose you use
Control.Invoke. I believe Control.Involke uses SendMessage internally to
switch the thread context so the worker thread should be fully blocked in
the SendMessage call and it shouldn't use any resources. Maybe the problem
is the way you pass the data.
This is just a guess.
If you post a code snippet how do you actually notify the UI thread and
marshal the data then it would be easier for the people in the group to help
you out.

B\rgds
100
 
-----Original Message-----
Hi Kueishiong,
I missed the thread where the solution was build and I don't know how you
notify the main thread and marshal the data. Anyway, I suppose you use
Control.Invoke. I believe Control.Involke uses SendMessage internally to
switch the thread context so the worker thread should be fully blocked in
the SendMessage call and it shouldn't use any resources. Maybe the problem
is the way you pass the data.
This is just a guess.
If you post a code snippet how do you actually notify the UI thread and
marshal the data then it would be easier for the people in the group to help
you out.

B\rgds
100

Here is the entire test program in C++. The object that
is performing the operation in the worker thread is the
same as the object in the main thread that is being
notified (i.e. instance of Form1 in this case). Could
this be the problem?
----------------------------------------------------------
#pragma once


namespace TestForm2
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Diagnostics;
using namespace System::Threading;

/// <summary>
/// Form1 的摘要
///
/// 警告: 如果您變更這個類別的名稱,就必須變更與這個類別
所依據之所有 .resx 檔案關聯的
/// Managed 資源編譯器工具的 'Resource File Name' 屬
性。
/// 否則,這些設計工具將無法與這個表單關聯的當地語系化資
源正確互動。
/// </summary>
public __gc class Form1 : public
System::Windows::Forms::Form
{
private: EventHandler *tComplete;
public:
Form1(void)
{
tComplete = new EventHandler
(this, &Form1::tcomplete);

InitializeComponent();
}

protected:
void Dispose(Boolean disposing)
{
if (disposing && components)
{
components->Dispose();
}
__super::Dispose(disposing);
}
private: System::Windows::Forms::Button *
button1;

private:
/// <summary>
/// 設計工具所需的變數。
/// </summary>
System::ComponentModel::Container *
components;

/// <summary>
/// 此為設計工具支援所必須的方法 - 請勿使用程式
碼編輯器修改
/// 這個方法的內容。
/// </summary>
void InitializeComponent(void)
{
this->button1 = new
System::Windows::Forms::Button();
this->SuspendLayout();
//
// button1
//
this->button1->Location =
System::Drawing::Point(112, 80);
this->button1->Name = S"button1";
this->button1->TabIndex = 0;
this->button1->Text = S"button1";
this->button1->Click += new
System::EventHandler(this, button1_Click);
//
// Form1
//
this->AutoScaleBaseSize =
System::Drawing::Size(5, 15);
this->ClientSize =
System::Drawing::Size(292, 266);
this->Controls->Add(this-
button1);
this->Name = S"Form1";
this->Text = S"Form1";
this->ResumeLayout(false);

}
private: System::Void button1_Click
(System::Object * sender, System::EventArgs * e)
{
Thread *t = new Thread(new
ThreadStart(this, threadproc));
t->IsBackground = true;
this->button1->Enabled = false;
t->Start();
}
private: void threadproc()
{
// do web request here
tComplete->Invoke(this,
EventArgs::Empty);
}
private: void tcomplete(System::Object *
sender, System::EventArgs * e)
{
Debug::WriteLine("enter
tcompleter");
this->button1->Enabled = true;
// do data display here
}
};
}
 
I create another control object to start the worker
thread so that the object in the main thread that is
notified will be different from the object that creates
the worker thread. But the result is the same. The data
display is very slow.

Notice that I use

tComplete->Invoke(this, EventArgs::Empty);

to invoke the eventhandler instead of using

Invoke(tComplete);

because the later causes a
'System.InvalidOperationException'

and the message is
"The window handle must be established before you
can use Invoke or InvokeAsync".

What is going on here? Is this the one that causes the
problem?

Regards,
Kueishiong Tu
 
I have resolved the problem. After I change the invoke
call from
tComplete->Invoke(this, EventArgs::Empty);
to
Invoke(tComplete);
Everything works fine. What is the difference between
these calls?

Also if I start the worker thread from a control object,
I couldn't use Invoke(tComplete) call. If I do, I got a
'System.InvalidOperationException'
and the error message is
"The window handle must be established before you can
call Invoke and InvokeAsync". What's going on here?
Following is the complete test program that starts the
worker thread from a control object.
----------------------------------------------------------
#pragma once


namespace TestForm4
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Threading;
using namespace System::Diagnostics;

public __gc class Tobj : public Control
{
private: EventHandler *tComplete;

public: Tobj()
{
tComplete = new EventHandler(this,
&Tobj::tcomplete);
}

public: __event EventHandler *fcomplete;

/// <summary>
/// This method is called by the
background thread when it has finished
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>

private: void tcomplete(System::Object
*sender, System::EventArgs* e)
{
Debug::WriteLine("enter
tcomplete");
fcomplete(sender, e);
}

public: System::Void dothread()
{
Debug::WriteLine(this, "enter
dothread this=");
Thread* tThread = new Thread(new
ThreadStart(this, tThreadProc));
tThread->Start();
}

private: System::Void tThreadProc()
{
// do web request here

// notify the work is complete
q
//tComplete->Invoke(this,
EventArgs::Empty);

Invoke(tComplete);
}
};

/// <summary>
/// Form1 的摘要
///
/// 警告:
如果您變更這個&#
39006;別的名稱,就&#24
517;須變更與這個&#3900
6;別
所依據之所有 .resx
檔案關聯的
/// Managed
資源編譯器工具&#
30340; 'Resource File Name' 屬
性。
///
否則,這些設計&#
24037;具將無法與這&#20
491;表單關聯的當&#2232
0;語系化資
源正確互動。
/// </summary>
public __gc class Form1 : public
System::Windows::Forms::Form
{
private: Tobj *tobj;
public:
Form1(void)
{
this->tobj = new Tobj();
this->tobj->fcomplete += new
System::EventHandler(this, &Form1::rcomplete);
InitializeComponent();
}

protected:
void Dispose(Boolean disposing)
{
if (disposing && components)
{
components->Dispose();
}
__super::Dispose(disposing);
}
private: System::Windows::Forms::Button *
button1;

private:
/// <summary>
///
設計工具所需的&#
35722;數。
/// </summary>
System::ComponentModel::Container *
components;

/// <summary>
///
此為設計工具支&#
25588;所必須的方法 -
請勿使用程式
碼編輯器修改
///
這個方法的內容&#
12290;
/// </summary>
void InitializeComponent(void)
{
this->button1 = new
System::Windows::Forms::Button();
this->SuspendLayout();
//
// button1
//
this->button1->Location =
System::Drawing::Point(40, 0);
this->button1->Name = S"button1";
this->button1->TabIndex = 0;
this->button1->Text = S"button1";
this->button1->Click += new
System::EventHandler(this, button1_Click);
//
// Form1
//
this->AutoScaleBaseSize =
System::Drawing::Size(5, 15);
this->ClientSize =
System::Drawing::Size(292, 266);
this->Controls->Add(this-
button1);
this->Name = S"Form1";
this->Text = S"Form1";
this->ResumeLayout(false);

}
private: System::Void button1_Click
(System::Object * sender, System::EventArgs * e)
{
tobj->dothread();
}
private: void rcomplete(System::Object *sender,
System::EventArgs* e)
{
Debug::WriteLine("enter
rcomplete");
};
};
}



..
 
I have resolved the problem. After I change the invoke
call from
tComplete->Invoke(this, EventArgs::Empty);
to
Invoke(tComplete);
Everything works fine. What is the difference between
these calls?

Also if I start the worker thread from a control object,
I couldn't use Invoke(tComplete) call. If I do, I got a
'System.InvalidOperationException'
and the error message is
"The window handle must be established before you can
call Invoke and InvokeAsync". What's going on here?
Following is the complete test program using a control
object to start the worker thread.
----------------------------------------------------------
#pragma once


namespace TestForm4
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Threading;
using namespace System::Diagnostics;

public __gc class Tobj : public Control
{
private: EventHandler *tComplete;

public: Tobj()
{
tComplete = new EventHandler(this,
&Tobj::tcomplete);
}

public: __event EventHandler *fcomplete;

/// <summary>
/// This method is called by the
/// background thread when it has finished
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>

private: void tcomplete(System::Object
*sender, System::EventArgs* e)
{
Debug::WriteLine("enter tcomplete");
fcomplete(sender, e);
}

public: System::Void dothread()
{
Debug::WriteLine(this, "enter dothread
this=");
Thread* tThread = new Thread(new
ThreadStart(this, tThreadProc));
tThread->Start();
}

private: System::Void tThreadProc()
{
// do web request here

// notify the work is complete
// The following call works
tComplete->Invoke(this,
EventArgs::Empty);

// The following call does not work ???
// It causes System.InvalidOperationException

//Invoke(tComplete);
}
};

/// <summary>
/// Form1 的摘要
/// </summary>
public __gc class Form1 : public
System::Windows::Forms::Form
{
private: Tobj *tobj;
public:
Form1(void)
{
this->tobj = new Tobj();
this->tobj->fcomplete += new
System::EventHandler(this, &Form1::rcomplete);
InitializeComponent();
}

protected:
void Dispose(Boolean disposing)
{
if (disposing && components)
{
components->Dispose();
}
__super::Dispose(disposing);
}
private: System::Windows::Forms::Button *
button1;

private:
/// <summary>
///
設計工具所需的&#
35722;數。
/// </summary>
System::ComponentModel::Container *
components;

/// <summary>
void InitializeComponent(void)
{
this->button1 = new
System::Windows::Forms::Button();
this->SuspendLayout();
//
// button1
//
this->button1->Location=System::Drawing::Point
(40, 0);
this->button1->Name = S"button1";
this->button1->TabIndex = 0;
this->button1->Text = S"button1";
this->button1->Click += new
System::EventHandler(this, button1_Click);
//
// Form1
//
this->AutoScaleBaseSize =
System::Drawing::Size(5, 15);
this->ClientSize = System::Drawing::Size(292,
266);
this->Controls->Add(this->button1);
this->Name = S"Form1";
this->Text = S"Form1";
this->ResumeLayout(false);
}
private: System::Void button1_Click
(System::Object * sender, System::EventArgs * e)
{
tobj->dothread();
}
private: void rcomplete(System::Object *sender,
System::EventArgs* e)
{
Debug::WriteLine("enter rcomplete");
}
};
}



..
 
Hi Kueishiong Tu,
I have resolved the problem. After I change the invoke
call from
tComplete->Invoke(this, EventArgs::Empty);
to
Invoke(tComplete);
Everything works fine. What is the difference between
these calls?


Control.Invoke changes the thread and executes the target method in the
thread owning the underlying native control handle. Deleagte.Invoke just
invoke the targets and it executes in the context of the calling thread.
Also if I start the worker thread from a control object,
I couldn't use Invoke(tComplete) call. If I do, I got a
'System.InvalidOperationException'
and the error message is
"The window handle must be established before you can
call Invoke and InvokeAsync". What's going on here?
Following is the complete test program that starts the
worker thread from a control object.

This is because the underlying win control is created when you add the
control to the form calling Controls->Add(...). You didn't add the control
anywhere in the form's code. Control.Invoke needs native window control's
HWND because it uses SendMessage to switch the threads.

If you update the UI from the Form's code it looks more correct to call
Form.Invoke . Anyway in your case it is the same.

Even though I made some chages in the code in your situation it may be
overkill. Your code will work just fine if you add Conntrols.Add(tobj) after
you create the control.


#pragma once


namespace TestForm4
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Threading;
using namespace System::Diagnostics;

public __gc class Tobj : public Control
{
private: EventHandler *tComplete;


public: Tobj(EventHandler *complete)
{
tComplete = complete;
}




public: System::Void dothread()
{
Debug::WriteLine(this, "enter dothread this=");
Thread* tThread = new Thread(new
ThreadStart(this, tThreadProc));
tThread->Start();
}

private: System::Void tThreadProc()
{
// do web request here

tComplete(this, EventArgs->Empty);
}

};

/// <summary>
/// Form1 的摘要
///
///
警告:如果您變更這個&#3
9006;別的名稱,就必須&#35722
;更與這個類別所依據&#2
0043;所有 .resx 檔案關聯的
/// Managed 資源編譯器工具的
'Resource File Name' 屬性。
///
否則,這些設計工具&#23
559;無法與這個表單關聯
的當地語系化資源正&#30
906;互動。
/// </summary>
public __gc class Form1 : public System::Windows::Forms::Form
{
private: Tobj *tobj;
private: EventHandler *m_CompleteNotification;
public:
Form1(void)
{
this->m_CompleteNotification = new EventHandler(this, &Form1::rcomplete);
this->tobj = new Tobj(m_CompleteNotification);
Controls->Add(tobj);
InitializeComponent();
}

protected:
void Dispose(Boolean disposing)
{
if (disposing && components)
{
components->Dispose();
}
__super::Dispose(disposing);
}
private: System::Windows::Forms::Button *button1;

private:
/// <summary>
/// 設計工具所需的&#
35722;數。
/// </summary>
System::ComponentModel::Container *components;

/// <summary>
///
此為設計工具支援所&#24
517;須的方法 -
請勿使用程式碼編輯&#22
120;修改
/// 這個方法的內容。
/// </summary>
void InitializeComponent(void)
{
this->button1 = new
System::Windows::Forms::Button();
this->SuspendLayout();
//
// button1
//
this->button1->Location =
System::Drawing::Point(40, 0);
this->button1->Name = S"button1";
this->button1->TabIndex = 0;
this->button1->Text = S"button1";
this->button1->Click += new
System::EventHandler(this, button1_Click);
//
// Form1
//
this->AutoScaleBaseSize =
System::Drawing::Size(5, 15);
this->ClientSize =
System::Drawing::Size(292, 266);
this->Controls->Add(this->button1);
this->Name = S"Form1";
this->Text = S"Form1";
this->ResumeLayout(false);

}
private: System::Void button1_Click
(System::Object * sender, System::EventArgs * e)
{
tobj->dothread();
}
private: void rcomplete(System::Object *sender,
System::EventArgs* e)
{
//Changes the tread only if it is necessary
if(this->InvokeRequired)
{
Object *params[] = {sender, e};
this->Invoke(m_CompleteNotification, params);
return;
}
Debug::WriteLine("enter rcomplete");
};
};
}
 
Back
Top