Anyone know what may cause a function
to take longer inside a new thread,
rather than calling directly?
All things being equal one of the most common causes of slower execution from one thread to another occurs when the code uses a COM object that is configured to run in a single thread apartment (STA) and meets one of the following other conditions.
- It is called from a thread other than the one that instantiated it.
- It is called from a thread configured to run in a multithreaded apartment (MTA).
An expensive marshaling operation most occur on each and every access to the object. A factor of 4x slower is a completely reasonable symptom of this issue. Resolving this issue will be quite difficult if you continue to use the BeginInvoke
invocation mechanism. The reason is because that mechanism uses a ThreadPool
thread for execution which cannot be easily (or at all) switched to STA mode.
I think your best bet is to create your own thread pool in which you do have control over the apartment state. It is not as hard as it sounds. The following code uses the BlockingCollection data structure is available in .NET 4.0 or as part of the Reactive Extensions download.
Note: You will have to add code hardening yourself to make it more robust, support gracefully shutting down, etc.
public class CustomThreadPool
{
private BlockingCollection<WorkItem> m_WorkItems = new BlockingCollection<WorkItem>();
public CustomThreadPool(int poolSize)
{
for (int i = 0; i < poolSize; i++)
{
var thread = new Thread(Run);
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
public void QueueUserWorkItem(WaitCallback callback, object state)
{
m_WorkItems.Add(new WorkItem { Callback = callback, State = state });
}
private void Run()
{
while (true)
{
WorkItem item = m_WorkItems.Take();
item.Callback(item.State);
}
}
private class WorkItem
{
public WaitCallback Callback { get; set; }
public object State { get; set; }
}
}
And then instead of calling BeginInvoke
on the delegate you would do this.
myThreadPool.QueueUserWorkItem((state) => { myDelegate(/* arguments */); }, null);
But the most important thing to remember is that the COM objects must be instantiated on an STA thread and all further access must be made from that thread. Any deviation from this will result in a marshaling operation. If you choose to use the approach in this answer then you will have to create the COM objects in the delegate.