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<>
,你是在要求崩溃。
下面是阻塞用户代码: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<Action>
在工作线程上工作的问题,从主 UI 线程排队,我最终使用了ConcurrentQueue<Action>
,根据@Anders H 的建议。我会使用任务,从我对该主题所做的大量研究来看,它可以解决潜在的跨线程访问问题,但是,因为排队的“工作”必须按顺序完成并与 COM 对象相关,所以 ConcurrentQueue最终看起来暂时是一个不错的解决方案。但是,稍后将不得不重新访问。
【讨论】:
以上是关于BackgroundWorker 使用 COM 对象导致 UI 线程挂起的主要内容,如果未能解决你的问题,请参考以下文章
如何正确实现 BackgroundWorker 以通知用户任务正在进行中
BackgroundWorker 填充 DataGridView