Search code examples
c#wpfmultithreadingcombackgroundworker

BackgroundWorker Using a COM Object Results in UI Thread Hangup


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).


Solution

  • 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.