user35662
user35662

Reputation: 37

How we can use COM Object in multithreading environment?

I am working at WinForm Application which works fine with COM Object because it use single thread .But my system is also using progressBar which load data from COM Object ,here problem starts

Upvotes: 0

Views: 1891

Answers (1)

KBO
KBO

Reputation: 671

So, create, access and dispose you managed COM wrapper (RCW) in ONE dedicated background thread, i.e. all the COM wrapper access is done in the thread callback method.

Data or method calls have to be marshalled from/to the UI thread.

Update:

Depending on your performance and other requirements, you have two choices:

  1. Encapsulate the object creation, one or more consecutive method calls (RCW usage) and the object disposal in one thread call. So you can use the thread pool (e.g. Task.Factory.StartNew()) for every COM server call. You only have to ensure, that only one thread accesses the COM server at the same time, otherwise the threading issue occurs.
  2. Encapsulate the object creation, all method calls (RCW usage) and the object disposal in ONE dedicated background thread. You have to create a signalling mechanism to perform the COM server calls. A simple example is to use an enumeration for the different methods to be called, a queue and an event to wake-up the thread if there is a pending method call.

Example for choice 1:

Task.Factory.StartNew(() =>
{
  MyRCWType objServer = null;

  try
  {
    objServer = new MyRCWType(); // create COM wrapper object
    objServer.MyMethodCall1();
    objServer.MyMethodCall2();
  }
  catch(Exception ex)
  {
    // Handle exception
  }
  finally
  {
    if(objServer != null)
    {
      while(Marshal.ReleaseComObject(objDA) > 0); // dispose COM wrapper object
    }
  }
});

Example for choice 2:

private enum MethodCallEnum { None, Method1, Method2 };
private Queue<MethodCallEnum> _queue = new Queue<MethodCallEnum>();
private AutoResetEvent _evtQueue = new AutoResetEvent(false);
private object _syncRoot = new object();

private Thread _myRCWWorker;
private void CancellationTokenSource _cancelSource = new CancellationTokenSource();

// maybe in your main form
protected override void OnLoad(EventArgs e)
{  
  base.OnLoad(e);

  // create one and only background thread for RCW access
  _myRCWWorker = new Thread(() => DoRCWWork(_cancelSource.Token, _evtQueue));
  _myRCWWorker.IsBackground = true;
  _myRCWWorker.Start();
}

private void CallMethod1()
{
  Enqueue(MethodCallEnum.Method1);
}

private void Enqueue(MethodCallEnum m)
{
  lock(_syncRoot)
  {
    _queue.Enqueue(m);
  }
  _evtQueue.Set(); // signal new method call
}

private MethodCallEnum Dequeue()
{
  MethodCallEnum m = MethodCallEnum.None;

  lock(_syncRoot)
  {
    if(_queue.Count > 0)
      m = _queue.Dequeue();
  }      
  return m;
}

private void DoRCWWork(CancellationToken cancelToken, WaitHandle evtQueue)
{
  MyRCWType objServer = null;
  int waitResult;

  try
  {
    objServer = new MyRCWType(); // create COM wrapper object

    while (!cancelToken.IsCancellationRequested)
    {
      if(evtQueue.WaitOne(200))
      {
        MethodCallEnum m = Dequeue();
        switch(m)
        {
          case MethodCallEnum.Method1:
            objServer.MyMethodCall1();
          break;

          case MethodCallEnum.Method2:
            objServer.MyMethodCall2();
          break;
        }
      }
    }
  }
  catch(Exception ex)
  {
    // Handle exception
  }
  finally
  {
    if(objServer != null)
    {
      while(Marshal.ReleaseComObject(objDA) > 0); // dispose COM wrapper object
    }
  }
}

This is only a very simple example to show the program flow. You can trigger COM server calls from any thread by calling the Enqueue() method, but the COM server is accessed only from the dedicated thread.

Upvotes: 1

Related Questions