Confusion: Threading & COM Interop

Z

Z D

Hello,

I'm having a strange problem that is probably due to my lack of
understanding of how threading & COM Interop works in a WinForms.NET
application.

Here's the situation:

I have a 3rd party COM component that takes about 5 seconds to run one of
its functions (Network IO bound call). Since I dont want my GUI to freeze
during this 5 seconds, I decided to:

1) Create a new class that accepts a callback delegate and the COM object
from the winforms application
2) This new class will then create a thread, and execute the long running
function on the COM object
3) when it completes, the callback delegate that was passed to the class is
invoked which then changes something in my GUI so I know its done.


I thought that this would work perfectly because now the IO bound call would
execute on another thread and would call a delegate which points to a
function in my GUI that would update a textbox in my GUI.


Unfortunately, when this new thread calls the COM objects function,
EVERYTHING in my app freezes until the COM objects function call completes!!
I dont understand why my GUI thread freezes since the COM call is happening
on another thread?? In debugger I can see the new thread that is created yet
my main GUI form freezes???

NOTE: If I comment out the function call to the COM object and replace it
with Thread.Sleep(5000) then it works as expected and my GUI does not
freeze. So my guess is somehow the COM object takes controll of all threads
and blocks everything until its completed?? I dont understand! What am I
doing wrong?


Also: I just discovered if I change my Form from STA to MTA then it works
fine! Why is this? I dont want to run my form as MTA because then drag/drop
OLE wont work.



Any suggestions? I'm sure I'm doing something wrong.

Here is the relavent code from my VB.NET winforms and my C# classes:

Thanks in advance for any help, guidance, comments, suggestions, etc!
-ZD



'----FUNCTION CALL FROM THE FORM:
Private Sub RenderMap()
Dim cb As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
_map.RenderMap(picMap.Height, picMap.Width, cb)
End Sub

'----CALL-BACK DELEGATE WHEN CALL COMPLETES:
Private Sub UpdateImage(ByVal arImage As Byte())
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End Sub



public class Map{

.......

public delegate void ImageUpdateHandler(Byte[] arImage);

....


//----RENDER MAP FUNCTION from the _map object:
public void RenderMap(int HEIGHT, int WIDTH, ImageUpdateHandler cb){
map.Height = HEIGHT;
map.Width = WIDTH;

PSTGI.GIS.Map.ImageRetriever c = new ImageRetriever(cb);
c.GetImage();
}

.....



//class nested within map class for convenience
public class ImageRetriever{

PSTGI.GIS.Map.ImageUpdateHandler _cb;
aims.MapClass _map;

//constructor
public ImageRetriever( ImageUpdateHandler cb, aims.MapClass
map){
_cb=cb;
_map=map;
}


//spawns a thread to get the image
public void GetImage(){
System.Threading.Thread t = new System.Threading.Thread(new
System.Threading.ThreadStart(DoIt));
t.Name="GetImageThread";
t.IsBackground=true;
t.Start();
}


// The funcion the new thread executes
public void DoIt(){
Byte[] arImage;
System.Net.WebClient wClient = new System.Net.WebClient();

//System.Threading.Thread.Sleep(5000);

_map.Refresh(); // THIS IS THE 5 SECOND FUNCTION CALL

string imgURL = _map.GetImageAsUrl();
arImage = wClient.DownloadData(imgURL);

object[] args;
args = new object[]{(object)arImage};
_cb.DynamicInvoke(args); //CALL THE DELEGATE IN THE WINFORM
SO GUI KNOWS ITS DONE
}

} //end of ImageRetreiver class

} //end of map class
 
N

Nicholas Paldino [.NET/C# MVP]

Z D,

See inline:
1) Create a new class that accepts a callback delegate and the COM object
from the winforms application
2) This new class will then create a thread, and execute the long running
function on the COM object

This is a bad idea. The COM object has thread affinity (most likely),
and then you are running it on another thread. If you don't marshal it
correctly, it will not work. Rather, pass the type of the com object and
create it on the new thread. Before you create it, set the ApartmentState
property of the current Thread instance (obtained by calling
Thread.CurrentThread) to ApartmentState.STA (if your COM component is an STA
component, or MTA if it is a MTA component, most likely, it is STA).

Then make the calls on the COM object.
3) when it completes, the callback delegate that was passed to the class
is invoked which then changes something in my GUI so I know its done.

As well as passing the type of the COM object and the callback, you
should also pass an implementation of ISynchronizeInvoke, which has an
Invoke method. The Control class implements this, and you should pass a
control from your UI (or the main form, if possible). You would pass the
delegate and the parameters to the Invoke method, and the call will be made
from the UI in the UI thread (which is causing some of your problems).

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)
I thought that this would work perfectly because now the IO bound call
would execute on another thread and would call a delegate which points to
a function in my GUI that would update a textbox in my GUI.


Unfortunately, when this new thread calls the COM objects function,
EVERYTHING in my app freezes until the COM objects function call
completes!! I dont understand why my GUI thread freezes since the COM call
is happening on another thread?? In debugger I can see the new thread that
is created yet my main GUI form freezes???

NOTE: If I comment out the function call to the COM object and replace it
with Thread.Sleep(5000) then it works as expected and my GUI does not
freeze. So my guess is somehow the COM object takes controll of all
threads and blocks everything until its completed?? I dont understand!
What am I doing wrong?


Also: I just discovered if I change my Form from STA to MTA then it works
fine! Why is this? I dont want to run my form as MTA because then
drag/drop OLE wont work.



Any suggestions? I'm sure I'm doing something wrong.

Here is the relavent code from my VB.NET winforms and my C# classes:

Thanks in advance for any help, guidance, comments, suggestions, etc!
-ZD



'----FUNCTION CALL FROM THE FORM:
Private Sub RenderMap()
Dim cb As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
_map.RenderMap(picMap.Height, picMap.Width, cb)
End Sub

'----CALL-BACK DELEGATE WHEN CALL COMPLETES:
Private Sub UpdateImage(ByVal arImage As Byte())
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End Sub



public class Map{

.......

public delegate void ImageUpdateHandler(Byte[] arImage);

....


//----RENDER MAP FUNCTION from the _map object:
public void RenderMap(int HEIGHT, int WIDTH, ImageUpdateHandler cb){
map.Height = HEIGHT;
map.Width = WIDTH;

PSTGI.GIS.Map.ImageRetriever c = new ImageRetriever(cb);
c.GetImage();
}

.....



//class nested within map class for convenience
public class ImageRetriever{

PSTGI.GIS.Map.ImageUpdateHandler _cb;
aims.MapClass _map;

//constructor
public ImageRetriever( ImageUpdateHandler cb, aims.MapClass
map){
_cb=cb;
_map=map;
}


//spawns a thread to get the image
public void GetImage(){
System.Threading.Thread t = new System.Threading.Thread(new
System.Threading.ThreadStart(DoIt));
t.Name="GetImageThread";
t.IsBackground=true;
t.Start();
}


// The funcion the new thread executes
public void DoIt(){
Byte[] arImage;
System.Net.WebClient wClient = new System.Net.WebClient();

//System.Threading.Thread.Sleep(5000);

_map.Refresh(); // THIS IS THE 5 SECOND FUNCTION CALL

string imgURL = _map.GetImageAsUrl();
arImage = wClient.DownloadData(imgURL);

object[] args;
args = new object[]{(object)arImage};
_cb.DynamicInvoke(args); //CALL THE DELEGATE IN THE
WINFORM SO GUI KNOWS ITS DONE
}

} //end of ImageRetreiver class

} //end of map class
 
Z

Z D

Hi Nicholas,

Thank's very much for your reply.

A) I did a test and only instantiated the COM object inside my new thread
and now things seem to work fine, even when I set my winform's main method
to STA.

I find this curious. Why would the COM object have an afinity to the thread
on which it was created? Is this how all .NET objects work? I always
thought that the thread that made the request to the object is the thread on
which it is run???

Am I correct in saying that the original problem was that even though I
called the method from the COM object on a new thread, it was being executed
on the thread on which it was created (Winform/GUI thread) and that is why
the GUI froze?

B)
As for the GUI being updated, I've changed the function the callback
delegate points to to look like this: (essentially I just check to see if an
invoke is required. If so, I call the same function using me.invoke so that
it will be called from the Form's thread. This works like a charm.

'-------Callback delegate points to this function. Called when the image is
ready.
Private Sub UpdateImage(ByVal arImage As Byte())
If Me.InvokeRequired() Then
Dim args As Object() = {arImage}
Dim dg As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
Me.Invoke(dg, args)
Else
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End If
End Sub


C)
Unfortunately, this solution doesn't quite work for me. If I instantiate
the COM object on the new thread then my main form cant access it. I need
to interact with it on my main form in order to render other parts of my
GUI. This COM object also maintains alot of state and is expensive to
instantiate.

Do you have any suggestions on how to architect this? I'm a little lost.

D) Do you know why my original problem worked fine when I set my main form
to MTA instead of STA? i.e. in my previous post, everything worked OK if I
set my form to MTA.

Thanks so much for all your help I really appreciate it!

-ZD




Nicholas Paldino said:
Z D,

See inline:
1) Create a new class that accepts a callback delegate and the COM object
from the winforms application
2) This new class will then create a thread, and execute the long running
function on the COM object

This is a bad idea. The COM object has thread affinity (most likely),
and then you are running it on another thread. If you don't marshal it
correctly, it will not work. Rather, pass the type of the com object and
create it on the new thread. Before you create it, set the ApartmentState
property of the current Thread instance (obtained by calling
Thread.CurrentThread) to ApartmentState.STA (if your COM component is an
STA component, or MTA if it is a MTA component, most likely, it is STA).

Then make the calls on the COM object.
3) when it completes, the callback delegate that was passed to the class
is invoked which then changes something in my GUI so I know its done.

As well as passing the type of the COM object and the callback, you
should also pass an implementation of ISynchronizeInvoke, which has an
Invoke method. The Control class implements this, and you should pass a
control from your UI (or the main form, if possible). You would pass the
delegate and the parameters to the Invoke method, and the call will be
made from the UI in the UI thread (which is causing some of your
problems).

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)
I thought that this would work perfectly because now the IO bound call
would execute on another thread and would call a delegate which points to
a function in my GUI that would update a textbox in my GUI.


Unfortunately, when this new thread calls the COM objects function,
EVERYTHING in my app freezes until the COM objects function call
completes!! I dont understand why my GUI thread freezes since the COM
call is happening on another thread?? In debugger I can see the new
thread that is created yet my main GUI form freezes???

NOTE: If I comment out the function call to the COM object and replace it
with Thread.Sleep(5000) then it works as expected and my GUI does not
freeze. So my guess is somehow the COM object takes controll of all
threads and blocks everything until its completed?? I dont understand!
What am I doing wrong?


Also: I just discovered if I change my Form from STA to MTA then it works
fine! Why is this? I dont want to run my form as MTA because then
drag/drop OLE wont work.



Any suggestions? I'm sure I'm doing something wrong.

Here is the relavent code from my VB.NET winforms and my C# classes:

Thanks in advance for any help, guidance, comments, suggestions, etc!
-ZD



'----FUNCTION CALL FROM THE FORM:
Private Sub RenderMap()
Dim cb As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
_map.RenderMap(picMap.Height, picMap.Width, cb)
End Sub

'----CALL-BACK DELEGATE WHEN CALL COMPLETES:
Private Sub UpdateImage(ByVal arImage As Byte())
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End Sub



public class Map{

.......

public delegate void ImageUpdateHandler(Byte[] arImage);

....


//----RENDER MAP FUNCTION from the _map object:
public void RenderMap(int HEIGHT, int WIDTH, ImageUpdateHandler cb){
map.Height = HEIGHT;
map.Width = WIDTH;

PSTGI.GIS.Map.ImageRetriever c = new ImageRetriever(cb);
c.GetImage();
}

.....



//class nested within map class for convenience
public class ImageRetriever{

PSTGI.GIS.Map.ImageUpdateHandler _cb;
aims.MapClass _map;

//constructor
public ImageRetriever( ImageUpdateHandler cb, aims.MapClass
map){
_cb=cb;
_map=map;
}


//spawns a thread to get the image
public void GetImage(){
System.Threading.Thread t = new
System.Threading.Thread(new System.Threading.ThreadStart(DoIt));
t.Name="GetImageThread";
t.IsBackground=true;
t.Start();
}


// The funcion the new thread executes
public void DoIt(){
Byte[] arImage;
System.Net.WebClient wClient = new System.Net.WebClient();

//System.Threading.Thread.Sleep(5000);

_map.Refresh(); // THIS IS THE 5 SECOND FUNCTION CALL

string imgURL = _map.GetImageAsUrl();
arImage = wClient.DownloadData(imgURL);

object[] args;
args = new object[]{(object)arImage};
_cb.DynamicInvoke(args); //CALL THE DELEGATE IN THE
WINFORM SO GUI KNOWS ITS DONE
}

} //end of ImageRetreiver class

} //end of map class
 
N

Nicholas Paldino [.NET/C# MVP]

Z D,

See inline:
A) I did a test and only instantiated the COM object inside my new thread
and now things seem to work fine, even when I set my winform's main method
to STA.

I find this curious. Why would the COM object have an afinity to the
thread on which it was created? Is this how all .NET objects work? I
always thought that the thread that made the request to the object is the
thread on which it is run???

COM objects have affinity to the apartment that they are created in.
They can be created in a Single Threaded Apartment (which has only one
thread and one thread only in it), or the Multi Threaded Apartment (which
has many threads in it). Most COM objects are STA objects, and are bound to
the thread that they are created in. The COM subsystem handles the
marshalling of calls across apartment boundaries (or custom proxies,
possibly). However, if you don't create the right proxies to make calls
across the boundaries, you will have adverse effects.
Am I correct in saying that the original problem was that even though I
called the method from the COM object on a new thread, it was being
executed on the thread on which it was created (Winform/GUI thread) and
that is why the GUI froze?

No, it was being called in the thread that you made the call from,
directly to the object in memory (instead of through a proxy) which was
created on another thread. Because the call wasn't set up correctly, this
contributed to the freezing.
B)
As for the GUI being updated, I've changed the function the callback
delegate points to to look like this: (essentially I just check to see if
an invoke is required. If so, I call the same function using me.invoke so
that it will be called from the Form's thread. This works like a charm.

'-------Callback delegate points to this function. Called when the image
is ready.
Private Sub UpdateImage(ByVal arImage As Byte())
If Me.InvokeRequired() Then
Dim args As Object() = {arImage}
Dim dg As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
Me.Invoke(dg, args)
Else
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End If
End Sub

Looks good.
C)
Unfortunately, this solution doesn't quite work for me. If I instantiate
the COM object on the new thread then my main form cant access it. I need
to interact with it on my main form in order to render other parts of my
GUI. This COM object also maintains alot of state and is expensive to
instantiate.

In this case, you are going to have to marshal a reference to the COM
object to the new threads. However, this will cause your GUI thread to
freeze up, as the call will be marshaled back to the Main UI thread, and the
long expensive call will still take place.
Do you have any suggestions on how to architect this? I'm a little lost.

D) Do you know why my original problem worked fine when I set my main form
to MTA instead of STA? i.e. in my previous post, everything worked OK if I
set my form to MTA.

You could set it to MTA, then what happens is that you will always be
accessing the call through a proxy (through COM), and it would work on all
threads (which default to MTA I believe). However, the problem with this is
that there are other components that you might use (if you use anything that
requires an ActiveX control) which would cause your UI to break. Generally
speaking, placing the MTAThread attribute on your UI thread handler method
is a bad idea.

What I would do is create the object on another thread, and then
register the object in the global interface table, then I would have that
thread wait on a WaitHandle (so that the thread stays alive, and therefore,
the object), which you then set when you want to dispose of it.

I'm working on a piece of code that would do this now. I'll post it
when I am done.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Thanks so much for all your help I really appreciate it!

-ZD




Nicholas Paldino said:
Z D,

See inline:
1) Create a new class that accepts a callback delegate and the COM
object from the winforms application
2) This new class will then create a thread, and execute the long
running function on the COM object

This is a bad idea. The COM object has thread affinity (most likely),
and then you are running it on another thread. If you don't marshal it
correctly, it will not work. Rather, pass the type of the com object and
create it on the new thread. Before you create it, set the
ApartmentState property of the current Thread instance (obtained by
calling Thread.CurrentThread) to ApartmentState.STA (if your COM
component is an STA component, or MTA if it is a MTA component, most
likely, it is STA).

Then make the calls on the COM object.
3) when it completes, the callback delegate that was passed to the class
is invoked which then changes something in my GUI so I know its done.

As well as passing the type of the COM object and the callback, you
should also pass an implementation of ISynchronizeInvoke, which has an
Invoke method. The Control class implements this, and you should pass a
control from your UI (or the main form, if possible). You would pass the
delegate and the parameters to the Invoke method, and the call will be
made from the UI in the UI thread (which is causing some of your
problems).

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)
I thought that this would work perfectly because now the IO bound call
would execute on another thread and would call a delegate which points
to a function in my GUI that would update a textbox in my GUI.


Unfortunately, when this new thread calls the COM objects function,
EVERYTHING in my app freezes until the COM objects function call
completes!! I dont understand why my GUI thread freezes since the COM
call is happening on another thread?? In debugger I can see the new
thread that is created yet my main GUI form freezes???

NOTE: If I comment out the function call to the COM object and replace
it with Thread.Sleep(5000) then it works as expected and my GUI does not
freeze. So my guess is somehow the COM object takes controll of all
threads and blocks everything until its completed?? I dont understand!
What am I doing wrong?


Also: I just discovered if I change my Form from STA to MTA then it
works fine! Why is this? I dont want to run my form as MTA because then
drag/drop OLE wont work.



Any suggestions? I'm sure I'm doing something wrong.

Here is the relavent code from my VB.NET winforms and my C# classes:

Thanks in advance for any help, guidance, comments, suggestions, etc!
-ZD



'----FUNCTION CALL FROM THE FORM:
Private Sub RenderMap()
Dim cb As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
_map.RenderMap(picMap.Height, picMap.Width, cb)
End Sub

'----CALL-BACK DELEGATE WHEN CALL COMPLETES:
Private Sub UpdateImage(ByVal arImage As Byte())
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End Sub



public class Map{

.......

public delegate void ImageUpdateHandler(Byte[] arImage);

....


//----RENDER MAP FUNCTION from the _map object:
public void RenderMap(int HEIGHT, int WIDTH, ImageUpdateHandler cb){
map.Height = HEIGHT;
map.Width = WIDTH;

PSTGI.GIS.Map.ImageRetriever c = new ImageRetriever(cb);
c.GetImage();
}

.....



//class nested within map class for convenience
public class ImageRetriever{

PSTGI.GIS.Map.ImageUpdateHandler _cb;
aims.MapClass _map;

//constructor
public ImageRetriever( ImageUpdateHandler cb, aims.MapClass
map){
_cb=cb;
_map=map;
}


//spawns a thread to get the image
public void GetImage(){
System.Threading.Thread t = new
System.Threading.Thread(new System.Threading.ThreadStart(DoIt));
t.Name="GetImageThread";
t.IsBackground=true;
t.Start();
}


// The funcion the new thread executes
public void DoIt(){
Byte[] arImage;
System.Net.WebClient wClient = new
System.Net.WebClient();

//System.Threading.Thread.Sleep(5000);

_map.Refresh(); // THIS IS THE 5 SECOND FUNCTION CALL

string imgURL = _map.GetImageAsUrl();
arImage = wClient.DownloadData(imgURL);

object[] args;
args = new object[]{(object)arImage};
_cb.DynamicInvoke(args); //CALL THE DELEGATE IN THE
WINFORM SO GUI KNOWS ITS DONE
}

} //end of ImageRetreiver class

} //end of map class
 
W

Willy Denoyette [MVP]

Before you start creating threads and initialize these to enter an STA, I
would suggest you to check the "Apartment" requirements of your COM object.
You can do this using oleview.exe
(http://www.microsoft.com/downloads/...0d-d9b2-4cb5-aeb6-45664be858b6&displaylang=en).
or using regedit and search the value of the 'ThreadingModel' value of your
component in the registry.
If your COM object is marked 'ThreadingModel' = 'Free', you have to
initialize your thread to enter an MTA.
If it's marked 'Both' you can initialize the thread as STA or MTA, taking
care that some (well most) COM objects marked as 'Both' are not tested in a
multithreaded application.
Anyway the main thread of a windows application must be an STA thread. This
required by Windows.Forms OLE drag and Drop support and some COM activeX UI
elements that could be placed on the form.
C)
Unfortunately, this solution doesn't quite work for me. If I instantiate
the COM object on the new thread then my main form cant access it. I need
to interact with it on my main form in order to render other parts of my
GUI. This COM object also maintains alot of state and is expensive to
instantiate.
No, you should update the UI from the alternative thread, the UI thread
should not use the COM interface, as it will freeze the UI during the call.
Don't forget to marshal the calls when updating the UI from other threads
than the main (UI) thread.
See http://www.pobox.com/~skeet/csharp/threads/winforms.shtml
The reason that it worked is a non issue, it's not that it works most of the
time that it will work all time.

Willy.


Z D said:
Hi Nicholas,

Thank's very much for your reply.

A) I did a test and only instantiated the COM object inside my new thread
and now things seem to work fine, even when I set my winform's main method
to STA.

I find this curious. Why would the COM object have an afinity to the
thread on which it was created? Is this how all .NET objects work? I
always thought that the thread that made the request to the object is the
thread on which it is run???

Am I correct in saying that the original problem was that even though I
called the method from the COM object on a new thread, it was being
executed on the thread on which it was created (Winform/GUI thread) and
that is why the GUI froze?

B)
As for the GUI being updated, I've changed the function the callback
delegate points to to look like this: (essentially I just check to see if
an invoke is required. If so, I call the same function using me.invoke so
that it will be called from the Form's thread. This works like a charm.

'-------Callback delegate points to this function. Called when the image
is ready.
Private Sub UpdateImage(ByVal arImage As Byte())
If Me.InvokeRequired() Then
Dim args As Object() = {arImage}
Dim dg As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
Me.Invoke(dg, args)
Else
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End If
End Sub


C)
Unfortunately, this solution doesn't quite work for me. If I instantiate
the COM object on the new thread then my main form cant access it. I need
to interact with it on my main form in order to render other parts of my
GUI. This COM object also maintains alot of state and is expensive to
instantiate.

Do you have any suggestions on how to architect this? I'm a little lost.

D) Do you know why my original problem worked fine when I set my main form
to MTA instead of STA? i.e. in my previous post, everything worked OK if I
set my form to MTA.

Thanks so much for all your help I really appreciate it!

-ZD




Nicholas Paldino said:
Z D,

See inline:
1) Create a new class that accepts a callback delegate and the COM
object from the winforms application
2) This new class will then create a thread, and execute the long
running function on the COM object

This is a bad idea. The COM object has thread affinity (most likely),
and then you are running it on another thread. If you don't marshal it
correctly, it will not work. Rather, pass the type of the com object and
create it on the new thread. Before you create it, set the
ApartmentState property of the current Thread instance (obtained by
calling Thread.CurrentThread) to ApartmentState.STA (if your COM
component is an STA component, or MTA if it is a MTA component, most
likely, it is STA).

Then make the calls on the COM object.
3) when it completes, the callback delegate that was passed to the class
is invoked which then changes something in my GUI so I know its done.

As well as passing the type of the COM object and the callback, you
should also pass an implementation of ISynchronizeInvoke, which has an
Invoke method. The Control class implements this, and you should pass a
control from your UI (or the main form, if possible). You would pass the
delegate and the parameters to the Invoke method, and the call will be
made from the UI in the UI thread (which is causing some of your
problems).

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)
I thought that this would work perfectly because now the IO bound call
would execute on another thread and would call a delegate which points
to a function in my GUI that would update a textbox in my GUI.


Unfortunately, when this new thread calls the COM objects function,
EVERYTHING in my app freezes until the COM objects function call
completes!! I dont understand why my GUI thread freezes since the COM
call is happening on another thread?? In debugger I can see the new
thread that is created yet my main GUI form freezes???

NOTE: If I comment out the function call to the COM object and replace
it with Thread.Sleep(5000) then it works as expected and my GUI does not
freeze. So my guess is somehow the COM object takes controll of all
threads and blocks everything until its completed?? I dont understand!
What am I doing wrong?


Also: I just discovered if I change my Form from STA to MTA then it
works fine! Why is this? I dont want to run my form as MTA because then
drag/drop OLE wont work.



Any suggestions? I'm sure I'm doing something wrong.

Here is the relavent code from my VB.NET winforms and my C# classes:

Thanks in advance for any help, guidance, comments, suggestions, etc!
-ZD



'----FUNCTION CALL FROM THE FORM:
Private Sub RenderMap()
Dim cb As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
_map.RenderMap(picMap.Height, picMap.Width, cb)
End Sub

'----CALL-BACK DELEGATE WHEN CALL COMPLETES:
Private Sub UpdateImage(ByVal arImage As Byte())
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End Sub



public class Map{

.......

public delegate void ImageUpdateHandler(Byte[] arImage);

....


//----RENDER MAP FUNCTION from the _map object:
public void RenderMap(int HEIGHT, int WIDTH, ImageUpdateHandler cb){
map.Height = HEIGHT;
map.Width = WIDTH;

PSTGI.GIS.Map.ImageRetriever c = new ImageRetriever(cb);
c.GetImage();
}

.....



//class nested within map class for convenience
public class ImageRetriever{

PSTGI.GIS.Map.ImageUpdateHandler _cb;
aims.MapClass _map;

//constructor
public ImageRetriever( ImageUpdateHandler cb, aims.MapClass
map){
_cb=cb;
_map=map;
}


//spawns a thread to get the image
public void GetImage(){
System.Threading.Thread t = new
System.Threading.Thread(new System.Threading.ThreadStart(DoIt));
t.Name="GetImageThread";
t.IsBackground=true;
t.Start();
}


// The funcion the new thread executes
public void DoIt(){
Byte[] arImage;
System.Net.WebClient wClient = new
System.Net.WebClient();

//System.Threading.Thread.Sleep(5000);

_map.Refresh(); // THIS IS THE 5 SECOND FUNCTION CALL

string imgURL = _map.GetImageAsUrl();
arImage = wClient.DownloadData(imgURL);

object[] args;
args = new object[]{(object)arImage};
_cb.DynamicInvoke(args); //CALL THE DELEGATE IN THE
WINFORM SO GUI KNOWS ITS DONE
}

} //end of ImageRetreiver class

} //end of map class
 
S

Sunny

Hi all,

Will you guys be so kind to tell me if this is a possible solution for
such a problems:

In my main app I start a new thread, and there I create a new invisible
form (named COMForm) and make ApplicationStart on it.

On COMForm I instaniate the COM object, and then from all other threads
I use COMForm.Invoke and COMForm.BeginInvoke to pass the calls in the
thread which have created the COM object?

Also, if this is a possible solution, is there a need to implement some
locking in the methods of COMForms, which are been called with Invoke
and BeginInvoke?. Or the calls will be queued and there is no need of
additional synchronization?

Or this is bad idea?

Thanks
Sunny
 
W

Willy Denoyette [MVP]

Inline ***

Willy.

Sunny said:
Hi all,

Will you guys be so kind to tell me if this is a possible solution for
such a problems:

In my main app I start a new thread, and there I create a new invisible
form (named COMForm) and make ApplicationStart on it.
*** Good thing, you create a new thread, initialize it as an STA thread and
let it pump messages.
On COMForm I instaniate the COM object, and then from all other threads
I use COMForm.Invoke and COMForm.BeginInvoke to pass the calls in the
thread which have created the COM object?
*** This one possibility, but only needed when making asynchronous calls
(BeginInvoke).
If you pass a reference to the COM object to another thread and use that
reference to call a method, the COM interop layer will marshal the call to
the correct apartment/thread, note that here the COM call is synchronous so
will block the caller for the duration of the call.
Also, if this is a possible solution, is there a need to implement some
locking in the methods of COMForms, which are been called with Invoke
and BeginInvoke?. Or the calls will be queued and there is no need of
additional synchronization?
**** No additional synchronization needed, the apartment is STA so only one
thread can enter it, additional locking wouldn't help anyway.
 
S

Sunny

Inline ***

Sunny

Inline ***

Willy.


*** Good thing, you create a new thread, initialize it as an STA thread and
let it pump messages.

*** This one possibility, but only needed when making asynchronous calls
(BeginInvoke).
If you pass a reference to the COM object to another thread and use that
reference to call a method, the COM interop layer will marshal the call to
the correct apartment/thread, note that here the COM call is synchronous so
will block the caller for the duration of the call.

I was not exact in my idea here. I create the COM object as private. The
access to its methods is done through wrapper methods of COMForm, which
do check for IsInvokeRequired and decide if they have to use
Invoke/BeginInvoke. I agree that this way the calling thread will block
for long duration invocations, but it depends on the intends, so for
these calls one can use BeginInvoke/EndInvoke to not block calling
thread.
**** No additional synchronization needed, the apartment is STA so only one
thread can enter it, additional locking wouldn't help anyway.

Ok, then this seems to be a working solution.

Thanks for confirming this.

Cheers
Sunny
 
Z

Z D

Hi Nicholas,

I was wondering if you ever completed the piece of code you were working on?

Thanks again for all your help.

-ZD


Nicholas Paldino said:
Z D,

See inline:
A) I did a test and only instantiated the COM object inside my new thread
and now things seem to work fine, even when I set my winform's main
method to STA.

I find this curious. Why would the COM object have an afinity to the
thread on which it was created? Is this how all .NET objects work? I
always thought that the thread that made the request to the object is the
thread on which it is run???

COM objects have affinity to the apartment that they are created in.
They can be created in a Single Threaded Apartment (which has only one
thread and one thread only in it), or the Multi Threaded Apartment (which
has many threads in it). Most COM objects are STA objects, and are bound
to the thread that they are created in. The COM subsystem handles the
marshalling of calls across apartment boundaries (or custom proxies,
possibly). However, if you don't create the right proxies to make calls
across the boundaries, you will have adverse effects.
Am I correct in saying that the original problem was that even though I
called the method from the COM object on a new thread, it was being
executed on the thread on which it was created (Winform/GUI thread) and
that is why the GUI froze?

No, it was being called in the thread that you made the call from,
directly to the object in memory (instead of through a proxy) which was
created on another thread. Because the call wasn't set up correctly, this
contributed to the freezing.
B)
As for the GUI being updated, I've changed the function the callback
delegate points to to look like this: (essentially I just check to see if
an invoke is required. If so, I call the same function using me.invoke so
that it will be called from the Form's thread. This works like a charm.

'-------Callback delegate points to this function. Called when the image
is ready.
Private Sub UpdateImage(ByVal arImage As Byte())
If Me.InvokeRequired() Then
Dim args As Object() = {arImage}
Dim dg As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
Me.Invoke(dg, args)
Else
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End If
End Sub

Looks good.
C)
Unfortunately, this solution doesn't quite work for me. If I instantiate
the COM object on the new thread then my main form cant access it. I
need to interact with it on my main form in order to render other parts
of my GUI. This COM object also maintains alot of state and is expensive
to instantiate.

In this case, you are going to have to marshal a reference to the COM
object to the new threads. However, this will cause your GUI thread to
freeze up, as the call will be marshaled back to the Main UI thread, and
the long expensive call will still take place.
Do you have any suggestions on how to architect this? I'm a little lost.

D) Do you know why my original problem worked fine when I set my main
form to MTA instead of STA? i.e. in my previous post, everything worked
OK if I set my form to MTA.

You could set it to MTA, then what happens is that you will always be
accessing the call through a proxy (through COM), and it would work on all
threads (which default to MTA I believe). However, the problem with this
is that there are other components that you might use (if you use anything
that requires an ActiveX control) which would cause your UI to break.
Generally speaking, placing the MTAThread attribute on your UI thread
handler method is a bad idea.

What I would do is create the object on another thread, and then
register the object in the global interface table, then I would have that
thread wait on a WaitHandle (so that the thread stays alive, and
therefore, the object), which you then set when you want to dispose of it.

I'm working on a piece of code that would do this now. I'll post it
when I am done.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Thanks so much for all your help I really appreciate it!

-ZD




Nicholas Paldino said:
Z D,

See inline:

1) Create a new class that accepts a callback delegate and the COM
object from the winforms application
2) This new class will then create a thread, and execute the long
running function on the COM object

This is a bad idea. The COM object has thread affinity (most
likely), and then you are running it on another thread. If you don't
marshal it correctly, it will not work. Rather, pass the type of the
com object and create it on the new thread. Before you create it, set
the ApartmentState property of the current Thread instance (obtained by
calling Thread.CurrentThread) to ApartmentState.STA (if your COM
component is an STA component, or MTA if it is a MTA component, most
likely, it is STA).

Then make the calls on the COM object.

3) when it completes, the callback delegate that was passed to the
class is invoked which then changes something in my GUI so I know its
done.

As well as passing the type of the COM object and the callback, you
should also pass an implementation of ISynchronizeInvoke, which has an
Invoke method. The Control class implements this, and you should pass a
control from your UI (or the main form, if possible). You would pass
the delegate and the parameters to the Invoke method, and the call will
be made from the UI in the UI thread (which is causing some of your
problems).

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)



I thought that this would work perfectly because now the IO bound call
would execute on another thread and would call a delegate which points
to a function in my GUI that would update a textbox in my GUI.


Unfortunately, when this new thread calls the COM objects function,
EVERYTHING in my app freezes until the COM objects function call
completes!! I dont understand why my GUI thread freezes since the COM
call is happening on another thread?? In debugger I can see the new
thread that is created yet my main GUI form freezes???

NOTE: If I comment out the function call to the COM object and replace
it with Thread.Sleep(5000) then it works as expected and my GUI does
not freeze. So my guess is somehow the COM object takes controll of
all threads and blocks everything until its completed?? I dont
understand! What am I doing wrong?


Also: I just discovered if I change my Form from STA to MTA then it
works fine! Why is this? I dont want to run my form as MTA because then
drag/drop OLE wont work.



Any suggestions? I'm sure I'm doing something wrong.

Here is the relavent code from my VB.NET winforms and my C# classes:

Thanks in advance for any help, guidance, comments, suggestions, etc!
-ZD



'----FUNCTION CALL FROM THE FORM:
Private Sub RenderMap()
Dim cb As New PSTGI.GIS.Map.ImageUpdateHandler(AddressOf
UpdateImage)
_map.RenderMap(picMap.Height, picMap.Width, cb)
End Sub

'----CALL-BACK DELEGATE WHEN CALL COMPLETES:
Private Sub UpdateImage(ByVal arImage As Byte())
Dim memStream As New System.IO.MemoryStream(arImage)
picMap.Image = Image.FromStream(memStream)
End Sub



public class Map{

.......

public delegate void ImageUpdateHandler(Byte[] arImage);

....


//----RENDER MAP FUNCTION from the _map object:
public void RenderMap(int HEIGHT, int WIDTH, ImageUpdateHandler cb){
map.Height = HEIGHT;
map.Width = WIDTH;

PSTGI.GIS.Map.ImageRetriever c = new ImageRetriever(cb);
c.GetImage();
}

.....



//class nested within map class for convenience
public class ImageRetriever{

PSTGI.GIS.Map.ImageUpdateHandler _cb;
aims.MapClass _map;

//constructor
public ImageRetriever( ImageUpdateHandler cb, aims.MapClass
map){
_cb=cb;
_map=map;
}


//spawns a thread to get the image
public void GetImage(){
System.Threading.Thread t = new
System.Threading.Thread(new System.Threading.ThreadStart(DoIt));
t.Name="GetImageThread";
t.IsBackground=true;
t.Start();
}


// The funcion the new thread executes
public void DoIt(){
Byte[] arImage;
System.Net.WebClient wClient = new
System.Net.WebClient();

//System.Threading.Thread.Sleep(5000);

_map.Refresh(); // THIS IS THE 5 SECOND FUNCTION CALL

string imgURL = _map.GetImageAsUrl();
arImage = wClient.DownloadData(imgURL);

object[] args;
args = new object[]{(object)arImage};
_cb.DynamicInvoke(args); //CALL THE DELEGATE IN THE
WINFORM SO GUI KNOWS ITS DONE
}

} //end of ImageRetreiver class

} //end of map class
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top