Reputation: 53
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);
}
}
Upvotes: 2
Views: 740
Reputation: 3799
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.
Upvotes: 1
Reputation: 69642
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.
Upvotes: 2
Reputation: 179799
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.
Upvotes: 0