我们如何在多线程环境中使用COM对象?

时间:2017-07-28 10:00:15

标签: c# multithreading com

我正在使用WinForm应用程序,它可以正常使用COM对象,因为它使用单线程。但我的系统也使用了从COM对象加载数据的progressBar,这里问题开始

  • 在加载数据期间(从COM对象获取数据)冻结GUI。

  • 为了解决这个问题,我尝试使用BackgroundWork解决了冻结GUI的问题。但后来我发现BackgroundWork默认使用Threadpool因为Multithread而在COM Object类中创建错误。我也试过这个代码创建单身,但仍无法正常工作

           Thread thread = new Thread(() => 
            {
                 Firmware_update_access();//it consumes Com Object
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
    

    COM组件是dll,当我尝试在包含的新线程下运行时会创建异常(thread.SetApartmentState(ApartmentState.STA).Exception是System.InvalidCastException: HRESULT:0x80004002(E_NOINTERFACE) )。 我的问题是我们如何创建一个新的STA线程(除了UI线程),它应该可以用于COM对象。

1 个答案:

答案 0 :(得分:1)

因此,在一个专用后台线程中创建,访问和配置托管COM包装器(RCW),即所有COM包装器访问都在线程回调方法中完成。

必须从/向UI线程编组数据或方法调用。

<强>更新

根据您的表现和其他要求,您有两种选择:

  1. 在一个线程调用中封装对象创建,一个或多个连续方法调用(RCW用法)和对象处理。因此,您可以为每个COM服务器调用使用线程池(例如Task.Factory.StartNew())。您只需要确保只有一个线程同时访问COM服务器,否则会出现线程问题。
  2. 在一个专用后台线程中封装对象创建,所有方法调用(RCW用法)和对象处理。您必须创建一个信号机制来执行COM服务器调用。一个简单的例子是对要调用的不同方法使用枚举,如果存在挂起的方法调用,则唤醒线程的队列和事件。
  3. 选择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
        }
      }
    });
    

    选择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
        }
      }
    }
    

    这只是一个显示程序流程的简单示例。您可以通过调用Enqueue()方法从任何线程触发COM服务器调用,但只能从专用线程访问COM服务器。