2

I have a MFC C++ Activex control and I am calling an Activex class function from a background thread DoWork() function. I found that it blocks the main thread. On the other hand, if I replace call to c++ COM function(which takes 5 seconds to execute) by Thread.Sleep(5000) in C#, it works in background thread.

I have tried many things like creating the instance of an activex class in a worker thread also. But it seems that the COM function always executes on the main thread. I have also attached the sample project.

/* Here's the C# code */
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void exit_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    private void cpp_Click(object sender, EventArgs e)
    {
        this.progressBar1.Style = ProgressBarStyle.Marquee;
        this.backgroundWorker1.RunWorkerAsync(1);
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        if (Convert.ToInt16(e.Argument) == 1)
        {
            // C++ function that takes 5 seconds to execute.
            this.axActivexComponent1.AboutBox();
        }
        else
        {
            /* Normal C# code, for this it works fine */
            System.Threading.Thread.Sleep(5000);
        }
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.progressBar1.Style = ProgressBarStyle.Blocks;
        MessageBox.Show("Thread Completed!!!");
    }

    private void csharp_Click(object sender, EventArgs e)
    {
        this.progressBar1.Style = ProgressBarStyle.Marquee;
        this.backgroundWorker1.RunWorkerAsync(2);
    }
}
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 2
    COM keeps objects thread-safe when they ask for it. Most do. If you want to run its code on another thread then you must create the object on that thread. – Hans Passant Feb 12 '15 at 11:03

3 Answers3

2

It is the COM concept of apartments - the ActiveX control belongs to single threaded apartment associated with the thread you refer to as "main thread". All calls to COM interfaces from that apartment are transferred to the main thread, including those to make from background worker threads.

COM provides you marshaling to create transparent proxy/stub channels to use those interfaces from other threads, but underneath they still block main thread since the ultimate call on the real object is anyway taking place there.

To use multiple threads, you need multiple threaded apartment aware COM objects (although all or almost all ActiveX controls use single threaded apartment model), or the control should provide custom marshaller on its side, such as free threaded marshaler, to be able to bypass default thread synchronization.

Roman R.
  • 68,205
  • 6
  • 94
  • 158
1

The COM object's threading model sounds like it is STA (single-threaded apartment):

COM objects marked as STA must be run on an STAThread, and cannot be passed to other threads, which is the case for any UI element in MFC C++. However, your program can still have many threads.

The apartment state is by default STA. For example, your main entry point from your client application is most likely marked as:

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {         

If you use a separate/non UI thread with the COM object, it needs to be explicitly in an STA thread, which isn't the case for background/thread-pooled worker threads. You have to create the thread yourself, and then instantiate the COM object on that thread E.g.:

Thread t = new Thread(new ThreadStart(ThreadProc));
t.SetApartmentState(ApartmentState.STA);

t.Start();

Also, an STA requirement is there must be some form of "message pump" (much like the one the main forms UI thread uses). See this for details.

Community
  • 1
  • 1
Jeb
  • 3,689
  • 5
  • 28
  • 45
  • Hi Jeb, Maybe I didn't mention, but I already tried creating my Thread, but it gives same result. Do you have any sample code for this? – Mohan Sawant Feb 13 '15 at 11:47
  • @MohanSawant: Are you creating your COM object in the dedicated thread "t" from the code sample? I have updated my answer a little (you may need a message pump). See also: http://stackoverflow.com/questions/10643652/accessing-com-object-from-thread-other-than-main-thread-slow-in-c-sharp?lq=1 – Jeb Feb 13 '15 at 13:13
  • I want my thread to do 2 things. 1. Instantiate a COM control object and add it to Form. 2. Execute the COM function. The problem is that Windows doesn't allow to add controls to form from any other thread. If I create a dummy form in threading function and add my control to it everything works fine as shown below. private void AddControl() { axControl = new AxControlLib.AxControl(); /* Dummy form */ Form frm = new Form(); frm.Visible = false; frm.Controls.Add(axControl); /* This is what I want */ this.Controls.Add(axControl); axControl.AboutBox(); } – Mohan Sawant Feb 20 '15 at 06:41
  • You will need to call Application.Run inside your new UI thread to create your message loop. The same cross-threading laws apply (e.g. you can only interact with the handle for a control on the thread inside which it was created), so you can't do anything on your other UI thread without switching contexts. Perhaps this will help? http://stackoverflow.com/questions/745057/how-to-start-a-ui-thread-in-c-sharp – Jeb Feb 20 '15 at 11:45
  • Did this approach help @MohanSawant? What was your final solution? – Jeb Apr 01 '15 at 08:14
  • It's still unresolved. I am not able to add the control to the form from the other thread. – Mohan Sawant Apr 02 '15 at 09:23
  • @MohanSawant - This looks interesting: http://stackoverflow.com/questions/439173/message-pumps-and-appdomains, and also https://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/ – Jeb Apr 02 '15 at 09:37
0

The usual cause for this is that the COM object isn't capable of running on worker threads. COM is ancient technology, well before threading was common. A lot of COM objects would break if called from multiple threads. Therefore, the COM default is that an object is called on the main thread unless it's marked as threadsafe.

You can't fix that from your C# code.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Well, doesn't sound very encouraging. Do you mean to say that I need to fix it in the COM code itself instead of bandaging in C#? – Mohan Sawant Feb 13 '15 at 11:50
  • Well, there's the bandage of creating a COM thread separate from your main C# thread. You'd now have this COM thread block for 5 seconds, not the main thread. – MSalters Feb 13 '15 at 11:55