BackgroundWorker 使用 COM 对象导致 UI 线程挂起

Posted

技术标签:

【中文标题】BackgroundWorker 使用 COM 对象导致 UI 线程挂起【英文标题】:BackgroundWorker Using a COM Object Results in UI Thread Hangup 【发布时间】:2020-10-20 00:09:47 【问题描述】:

我有一个专用类,其中包括一个 BackgroundWorker,它负责从队列中运行特定于类的操作 - 需要使用 COM 对象的操作。

在应用程序启动 (WPF) 时从 UI 线程在运行时创建专用类的对象。当调用该类的构造函数时,它会实例化一个 BackgroundWorker,它运行从 UI 线程分配的异步出队操作。

但是,当这些操作需要来自 COM 对象的数据时,我注意到 UI 线程正在等待 BackgroundWorker 完成操作,然后再对用户输入做出反应。

如何隔离,以便 UI 线程不受 COM 功能的影响,这些功能可能需要 10 秒才能完成?

代码:

public class User()
  private BackgroundWorker Worker;
  private Queue<Action> ActionQueue;
  private COM COMObject; // COM is an interface exposed by the COM referenced in VS project
  private bool Registered;

  public User()
    this.Registered = true;
    this.ActionQueue = new Queue<Action>();
    this.Worker = new BackgroundWorker();
    this.Worker.DoWork += new DoWorkEventHandler(DoWork);
    this.Worker.DoWork += new RunWorkerCompletedEventHandler(WorkerCompleted);
    this.Worker.Worker.WorkerSupportsCancellation = true;
    this.Worker.Worker.RunWorkerAsync();
    this.COMObject = new COM();
  

  private DoWork(object sender, DoWorkEventArgs e)
    // If there is something to be done (an action) in the queue
    if (ActionQueue.Count > 0)
      // Dequeue the action from the queue
      Action queuedAction = ActionQueue.Dequeue();

      // Do the action
      queuedAction();
    
  

  private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    // While this machine continues to be registered to the app...
    if (this.Registered)
    
        Worker.RunWorkerAsync();
    
  

  public void ConnectToDatabase()
    Action action = delegate 

      COMObject.Connect(); // function can take up to 10 seconds to return 
      
    ; // end of action delegate

    ActionQueue.Enqueue(action);
  

使用代码(在 UI 线程中):

User user = new User();
user.ConnectToDatabase();

在我的 UI 中,在应用程序启动期间,最多可以创建 10 个 User 对象并调用它们进行连接。如果我注释掉 User::ConnectToDatabase 中的 COMObject.Connect(); 行并用 Thread.Sleep(10000) 替换,UI 线程不会等待 10 多秒。但是,就像现在的代码一样,我注意到 COMObject.Connect(); 行确实会在 WPF 应用程序中的任何用户输入再次处理之前导致 10 多秒。

如何隔离,使与 COM 对象相关的功能不会影响 UI 线程的性能?

(注意:BackgroundWorker 排队的操作与 UI 线程没有交互。在这些操作中仅更改特定于类的属性)。

【问题讨论】:

为了记录,您不仅要直接在主线程上执行 COM 操作(new COM() 发生在 UI 线程上),而且您应该查找您最有可能放置的 STAThread 属性你的主要功能。另外,不要像在线程之间那样使用Queue&lt;&gt;,你是在要求崩溃。 下面是阻塞用户代码:this.COMObject = new COM();您可以将这行代码移至 WorkerCompleted 事件。 workerComp[leted 事件不必在 BackGroundWorker 中。所以我会移动到外部,以便其余代码可以访问数据。 @Blindy - Queue 包含特定于线程的操作(与任何其他线程的函数无关),这还会带来风险吗? 不管它持有什么,你在 UI 线程中排队并在后台工作线程中排队,这显然是灾难的根源。 使用 ConcurrentQueue 代替队列 【参考方案1】:

答案总是潜伏在 cmets 中 :)

正如@Blindy 和@jdweng 所指出的,new COM() 在主 UI 线程上被调用,而 COM 对象的所有功能都在不同的线程上使用。

此外,我确实使用 STAThread 属性 (this.Worker.SetApartmentState(ApartmentState.STA);) 设置了 COM 对象的线程。

而且,我确实从使用 BackgroundWorker 更改为实际的 Thread。

最后但并非最不重要的一点是,@Blindy 指出了使用Queue&lt;Action&gt; 在工作线程上工作的问题,从主 UI 线程排队,我最终使用了ConcurrentQueue&lt;Action&gt;,根据@Anders H 的建议。我会使用任务,从我对该主题所做的大量研究来看,它可以解决潜在的跨线程访问问题,但是,因为排队的“工作”必须按顺序完成并与 COM 对象相关,所以 ConcurrentQueue最终看起来暂时是一个不错的解决方案。但是,稍后将不得不重新访问。

【讨论】:

以上是关于BackgroundWorker 使用 COM 对象导致 UI 线程挂起的主要内容,如果未能解决你的问题,请参考以下文章

Wpf Backgroundworker

如何正确实现 BackgroundWorker 以通知用户任务正在进行中

BackgroundWorker 填充 DataGridView

线程封装组件(BackgroundWorker)和线程(Thread)

backgroundworker控件

C#中的BackgroundWorker控件