views:

152

answers:

3

Hi,

Sorry to ask such a basic question but I seem to have a brain freeze on this one! I'm calling a COM (ATL) object from my WPF project. The COM method might take a long time to complete. I thought I'd try and call it asychronously. I have a few demo lines that show the problem.

private void checkBox1_Checked(object sender, RoutedEventArgs e)
 {
      //DoSomeWork();
      AsyncDoWork caller = new AsyncDoWork(DoSomeWork);
      IAsyncResult result = caller.BeginInvoke(null, null);            
  }

private delegate void AsyncDoWork();
private void DoSomeWork()
{
   _Server.DoWork();
}

The ATL method DoWork is very exciting. It is:

STDMETHODIMP CSimpleObject::DoWork(void)
{
   Sleep(5000);
   return S_OK;
}

I had expectations that running this way would result in the checkbox being checked right away (instead of in 5 seconds) and me being able to move the WPF gui around the screen. I can't - for 5 seconds.

What am I doing wrong? I'm sure it's something pretty simple. Delegate signature wrong?

Thanks.

A: 

BeginInvoke is still going to execute your call on the same thread, just asynchronously*. You can either create a new Thread object:

Thread comthread = new Thread(new ThreadStart(delegate() { DoSomeWork(); }));
comthread.Start();

or try out .Net 4's new Task library:

Task.Factory.StartNew(() =>
{
    DoSomeWork();
});

which are essentially the same thing.**

*A delegate type's BeginInvoke method executes on the same thread as the caller, but in the background. I'm not sure if there are rules regarding what gets executed when, but it's certainly not in the order you want. However, asynchronous methods like BeginRead execute on a special thread separate from the main one.
**There is a slight difference - the Thread method will always create a new Thread object, whereas the Task system has a pool of threads to work with, which is in theory more efficient.

JustABill
JustABill, Thanks for the quick response. Starting a new thread did not solve the problem either. See my comment above; I think the problem is that the ATL object is living in a Single Threaded Apartment. Incidentally, once I replace the code to just sleep for 5 seconds and not make a COM call, the BeginInvoke worked fine (as did your new Thread(...) idea) and clearly showed that the work (sleep in this case) was being done on a worker thread. So I'm not seeing that "BeginInvoke method executes on the same thread...". I don't have the option of .Net 4.0 - Task.Factory sounds neat. Thxs.
Dave
A: 

I have done some more thinking and testing about this. There is nothing wrong with the C# code. If the ATL object is an STA object (as it was in my case), it will be called on the main thread, regardless of attempts by the C# code to call it on a worker thread. Changing the ATL object to an MTA object makes it possible to to call it asynchronously.

Dave
A: 

I'm sure you're right about the call to your ATL code getting marshaled to the GUI thread because the ATL code is STA, thereby blocking your GUI thread.

Two solutions:

  1. Rearchitect the ATL portion to be MTA, which may not be feasible, or
  2. Leave the ATL as STA but initially construct the COM object in a thread created for that purpose so it will get a different apartment.

A WPF application actually runs just fine with multiple UI threads, as long as each UI thread has manages its own part of the UI, and the parts are separated by HwndSource. In other words, the second thread that runs part of the UI implements a Win32 HWND which is then embedded in the portion of the UI run by the main thread.

If your COM object isn't itself a GUI object, then it should be very easy to construct it in a separate worker thread and leave it there. Since it is a STA object, all calls will be marshaled to the other thread.

Ray Burns
Thanks for the answer. I think it will actually be fairly easy to change the ATL portion to MTA. Just out of curiousity, do you have any references to WPF apps with multiple UI Threads?
Dave
I've done it in some of my own apps, and it isn't very hard. Here are some links that cover various aspects of it: http://blogs.msdn.com/b/changov/archive/2009/10/26/hosting-wpf-ui-cross-thread-and-cross-process.aspx http://msdn.microsoft.com/en-us/library/bb909794(VS.90).aspx http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/ http://www.drdobbs.com/windows/197003872
Ray Burns

related questions