Pitfalls With .Net Multithreading And COM Objects – Threads Must Have Compatible Apartment Models (MTA vs. STA)

Be alert when implementing multithreading .Net in conjunction with COM objects. Thread apartment models matter.

.Net threads have Multi Threaded Apartment (MTA) model by default. COM objects have Single Thread Apartment (STA). Calling on COM objects on .Net threads that you spawn may cause unpredicted result.

Multithreading in .Net is easily implemented based on either Thread or ThreadPool objects. Thread.Start() method spawns new thread which has Multi Threaded Apartment (MTA) model . ThreadPool.QueueUserWorkItem(myMethod) queues myMethod to be executed on available thread managed by it.

Thread object and COM

When spawning your own threads with Thread.Start() set Thread’s apartment model to STA if you plan calling COM object on it:

    1: Thread t = new Thread(DoWork);
    2: t.SetApartmentState(ApartmentState.STA);
    3: t.Start();

Note, spawning your own threads with Thread.Start() utilizes resources – mainly CPU, spawning too much threads may cause performance hit. Consider using ThreadPool object.

ThreadPool object and COM

All threads managed by ThreadPool objects are MTA threads. Apartment model of ThreadPool’s threads cannot be changed. Do not call COM objects on ThreadPool’s threads.

Possible designs and solutions

  • ASP.NET. Calling COM objects in ASP.NET pages configure AspCompat=true:

“Most COM components will work with ASP.NET…”

“…The AspCompat attribute forces the page to execute in STA mode. The runtime throws an exception if the compatibility tag is omitted and an STA component is referenced on the page.”

  • Client side (like WinForms)multithreading. Use ThreadPool with its MTA threads. Spawn another thread with Thread.Start() setting its apartment model to STA.
  • Custom server side multithreading. I’d consider using Remoting infrastructure. In case your server application should serve messages that are not covered by Remoting’s built in mechanisms I’d consider building my own Channel sink.
  • And the last but not the least. Do not forget to call Marshal.ReleaseComObject(obj) to dispose the COM objects created on your the threads.