¿Cómo actualizar la GUI con backgroundworker?

Resuelto Mads Andersen asked hace 14 años • 7 respuestas

Pasé todo el día intentando que mi aplicación utilizara subprocesos, pero no tuve suerte. He leído mucha documentación al respecto y todavía recibo muchos errores, así que espero que puedan ayudarme.

Tengo un método que requiere mucho tiempo y que llama a la base de datos y actualiza la GUI. Esto tiene que suceder todo el tiempo (o aproximadamente cada 30 segundos).

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();
    }
}

Con este enfoque obtengo una excepción porque el trabajador en segundo plano no es un subproceso STA (pero por lo que puedo entender, esto es lo que debo usar). Lo intenté con un hilo de STA y dio otros errores.

Creo que el problema se debe a que intento actualizar la GUI mientras hago la llamada a la base de datos (en el hilo de fondo). Solo debería hacer la llamada a la base de datos y luego, de alguna manera, debería volver al hilo principal. Una vez que se haya ejecutado el hilo principal, debería volver al hilo de fondo y así sucesivamente. Pero no veo cómo hacer eso.

La aplicación debería actualizar la GUI inmediatamente después de la llamada a la base de datos. Los eventos de despido no parecen funcionar. El hilo de fondo simplemente los ingresa.

EDITAR:

Algunas respuestas realmente geniales :) Este es el nuevo código:

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
}

}

¿Pero cómo puedo hacer que esto se ejecute cada 10 segundos? System.Threading.Thread.Sleep(10000) simplemente hará que mi GUI se congele y el bucle while (verdadero) en Update() como se sugiere da una excepción (el hilo está demasiado ocupado).

Mads Andersen avatar Dec 08 '09 03:12 Mads Andersen
Aceptado

Debe declarar y configurar BackgroundWorker una vez y luego invocar el método RunWorkerAsync dentro de su bucle...

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();
    }
}
kiwipom avatar Dec 07 '2009 20:12 kiwipom

Tienes que usar la propiedad Control.InvokeRequired para determinar si estás en un hilo en segundo plano. Luego, debe invocar la lógica que modificó su interfaz de usuario a través del método Control.Invoke para forzar que las operaciones de la interfaz de usuario se realicen en el hilo principal. Para ello, cree un delegado y páselo al método Control.Invoke . El problema aquí es que necesitas algún objeto derivado de Control para llamar a estos métodos.

Editar : como publicó otro usuario, si puede esperar hasta el evento BackgroundWorker.Completed para actualizar su interfaz de usuario, puede suscribirse a ese evento y llamar a su código de interfaz de usuario directamente. Se llama a BackgroundWorker_Completed en el hilo principal de la aplicación. mi código supone que desea realizar actualizaciones durante la operación. Una alternativa a mi método es suscribirse al evento BwackgroundWorker.ProgressChanged , pero creo que aún necesitarás llamar a Invoke para actualizar tu interfaz de usuario en ese caso.

Por ejemplo

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()
       }
    }
}
Mike Marshall avatar Dec 07 '2009 20:12 Mike Marshall