I have a dedicated class that includes a BackgroundWorker that is responsible for running class-specific actions from a queue - actions which require use of a COM object.
Objects the dedicated class are created during runtime from the UI thread when the application starts up (WPF). When the class' constructor is called, it instantiates a BackgroundWorker that runs asynchronously dequeuing Actions assigned from the UI thread.
However, when these Actions require data resulting from the COM object, I notice that the UI thread is waiting on the BackgroundWorker to finish the Action before reacting to user input.
How can I isolate so that the UI thread is not impacted by the COM's functions that can take up to 10 seconds to complete?
Code:
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);
}
}
Use Code (in UI thread):
User user = new User();
user.ConnectToDatabase();
In my UI, during application startup, there can be up to 10 User
objects created and called to connect. If I comment out the COMObject.Connect();
line in User::ConnectToDatabase and replace with Thread.Sleep(10000)
the UI thread does not wait 10+ seconds. But, as is the code now, I notice that the COMObject.Connect();
line does result in 10+ seconds before any user input in the WPF app is processed again.
How can I isolate so that the functions related to the COM object do not impact the performance of the UI thread?
(Note: there is no interaction with the UI thread from the actions queued with the BackgroundWorker. Only class-specific properties are changed in those actions).
The answers always lurk in the comments :)
As @Blindy and @jdweng pointed out, the new COM()
was being called on the main UI thread, whereas all the functionality of the COM object was being used on a different thread.
In addition, I did set the COM object's thread with the STAThread attribute (this.Worker.SetApartmentState(ApartmentState.STA);
).
And, I did change from using a BackgroundWorker to an actual Thread.
And, last, but not least, as @Blindy called out the issue with using a Queue<Action>
to do work on the Worker thread, queued from the main UI thread, I did end up using a ConcurrentQueue<Action>
, per @Anders H's suggestion. I would have used Tasks, which from the amount of research I did on the topic would have solved the cross-thread access potential issue, but, because queued "work" would have to be done sequentially and relating to the COM object, the ConcurrentQueue ended up seeming like a decent solution for the time being. But, will have to revisit this later on.