Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update GUI with backgroundworker?

I have spent the whole day trying to make my application use threads but with no luck. I have read much documentation about it and I still get lots of errors, so I hope you can help me.

I have one big time consuming method which calls the database and updates the GUI. This has to happen all the time(or about every 30 seconds).

public class UpdateController
{
    private UserController _userController;

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
    }

    public void Update()
    {
        BackgroundWorker backgroundWorker = new BackgroundWorker();
        while(true)
        {
            backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            backgroundWorker.RunWorkerAsync();
        }     
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        _userController.UpdateUsersOnMap();
    }
}

With this approach I get an exception because the backgroundworker is not and STA thread(but from what I can understand this is what I should use). I have tried with a STA thread and that gave other errors.

I think the problem is because I try to update the GUI while doing the database call(in the background thread). I should only be doing the database call and then somehow it should switch back to the main thread. After the main thread has executed it should go back to the background thread and so on. But I can't see how to do that.

The application should update the GUI right after the database call. Firering events don't seem to work. The backgroundthread just enters them.

EDIT:

Some really great answers :) This is the new code:

public class UpdateController{
private UserController _userController;
private BackgroundWorker _backgroundWorker;

public UpdateController(LoginController loginController, UserController userController)
{
    _userController = userController;
    loginController.LoginEvent += Update;
    _backgroundWorker = new BackgroundWorker();
    _backgroundWorker.DoWork += backgroundWorker_DoWork;
    _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}

public void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    _userController.UpdateUsersOnMap();
}

public void Update()
{   
    _backgroundWorker.RunWorkerAsync();
}

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //UI update
    System.Threading.Thread.Sleep(10000);
    Update();
}

public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Big database task
}

}

But how can I make this run every 10 second? System.Threading.Thread.Sleep(10000) will just make my GUI freeze and while(true) loop in Update() as suggested gives an exception(Thread too busy).

like image 503
Mads Andersen Avatar asked Dec 07 '09 20:12

Mads Andersen


People also ask

Can BackgroundWorker update UI?

Edit: As another user posted, if yo you can wait to the BackgroundWorker. Completed event to update your UI then you can subscribe to that event and call your UI code directly. BackgroundWorker_Completed is called on the main app thread. my code assumes you want to do updates during the operation.

How do I use BackgroundWorker?

The steps are extremely simple: Create a BackgroundWorker object. Tell the BackgroundWorker object what task to run on the background thread (the DoWork function). Tell it what function to run on the UI thread when the work is complete (the RunWorkerCompleted function).

What is the difference between BackgroundWorker and thread?

A BackgroundWorker is a ready to use class in WinForms allowing you to execute tasks on background threads which avoids freezing the UI and in addition to this allows you to easily marshal the execution of the success callback on the main thread which gives you the possibility to update the user interface with the ...

What is BackgroundWorker in WinForms?

The BackgroundWorker class exposes the DoWork event, to which your worker thread is attached through a DoWork event handler. The DoWork event handler takes a DoWorkEventArgs parameter, which has an Argument property.


3 Answers

You need to declare and configure the BackgroundWorker once - then Invoke the RunWorkerAsync method within your loop...

public class UpdateController
{
    private UserController _userController;
    private BackgroundWorker _backgroundWorker;

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
        _backgroundWorker.WorkerReportsProgress= true;
    }

    public void Update()
    {
         _backgroundWorker.RunWorkerAsync();    
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        while (true)
        {
        // Do the long-duration work here, and optionally
        // send the update back to the UI thread...
        int p = 0;// set your progress if appropriate
        object param = "something"; // use this to pass any additional parameter back to the UI
        _backgroundWorker.ReportProgress(p, param);
        }
    }

    // This event handler updates the UI
    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the UI here
//        _userController.UpdateUsersOnMap();
    }
}
like image 121
kiwipom Avatar answered Oct 10 '22 12:10

kiwipom


You have to use the Control.InvokeRequired property to determine if you are on a background thread. Then you need to invoke your logic that modified your UI via the Control.Invoke method to force your UI operations to occur on the main thread. You do this by creating a delegate and passing it to the Control.Invoke method. The catch here is you need some object derived from Control to call these methods.

Edit: As another user posted, if yo you can wait to the BackgroundWorker.Completed event to update your UI then you can subscribe to that event and call your UI code directly. BackgroundWorker_Completed is called on the main app thread. my code assumes you want to do updates during the operation. One alternative to my method is to subscribe to the BwackgroundWorker.ProgressChanged event, but I believe you'll need to still call Invoke to update your UI in that case.

for example

public class UpdateController
{
    private UserController _userController;        
    BackgroundWorker backgroundWorker = new BackgroundWorker();

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
    }

    public void Update()
    {                        
         // The while loop was unecessary here
         backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
         backgroundWorker.RunWorkerAsync();                 
    }

    public delegate void DoUIWorkHandler();


    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       // You must check here if your are executing on a background thread.
       // UI operations are only allowed on the main application thread
       if (someControlOnMyForm.InvokeRequired)
       {
           // This is how you force your logic to be called on the main
           // application thread
           someControlOnMyForm.Invoke(new             
                      DoUIWorkHandler(_userController.UpdateUsersOnMap);
       }
       else
       {
           _userController.UpdateUsersOnMap()
       }
    }
}
like image 32
Mike Marshall Avatar answered Oct 10 '22 14:10

Mike Marshall


You should remove the while(true), you are adding infinite event handlers and invoking them infinite times.

like image 5
µBio Avatar answered Oct 10 '22 14:10

µBio