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 [.NET/C# MVP]" <mvp@spam.guard.caspershouse.com> wrote in
message news:ufQ52LGuEHA.1472@TK2MSFTNGP10.phx.gbl...[color=blue]
>Z D,
>
> See inline:
>[color=green]
>> 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???[/color]
>
> 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.
>[color=green]
>> 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?[/color]
>
> 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.
>[color=green]
>> 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[/color]
>
> Looks good.
>[color=green]
>> 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.[/color]
>
> 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.
>[color=green]
>> 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.[/color]
>
> 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]
> -
mvp@spam.guard.caspershouse.com
>
>[color=green]
>>
>> Thanks so much for all your help I really appreciate it!
>>
>> -ZD
>>
>>
>>
>>
>> "Nicholas Paldino [.NET/C# MVP]" <mvp@spam.guard.caspershouse.com> wrote
>> in message news:eNLrSKFuEHA.1308@tk2msftngp13.phx.gbl...[color=darkred]
>>>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]
>>> -
mvp@spam.guard.caspershouse.com
>>>
>>>>
>>>>
>>>> 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
>>>>
>>>>
>>>>
>>>>
>>>
>>>[/color]
>>
>>[/color]
>
>[/color]