thread safe make illegal how form for ejemplo cross check calls c# wpf multithreading backgroundworker

safe - thread invoke c#



¿Cómo actualizar la GUI con backgroundworker? (7)

Además de los comentarios anteriores, eche un vistazo a www.albahari.com/threading , el mejor documento de enhebrado que encontrará. Le enseñará a usar el BackgroundWorker correctamente.

Debe actualizar la GUI cuando BackgroundWorker desencadena el evento Completed (que se invoca en el subproceso de la interfaz de usuario para que sea más fácil para usted, para que no tenga que hacer Control. Invoque a sí mismo).

Me he pasado todo el día intentando que mi aplicación use hilos pero sin suerte. He leído mucha documentación al respecto y todavía recibo muchos errores, así que espero que puedan ayudarme.

Tengo un gran método que consume mucho tiempo 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 backgroundworker no lo es y el hilo STA (pero por lo que puedo entender, esto es lo que debería usar). Lo intenté con un hilo STA y eso dio otros errores.

Creo que el problema se debe a que trato de 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. Después de que se ha ejecutado el hilo principal, debe volver al hilo de fondo y así sucesivamente. Pero no puedo ver cómo hacer eso.

La aplicación debe actualizar la GUI justo después de la llamada a la base de datos. Los incendios no parecen funcionar. El hilo de fondo acaba de entrar en ellos.

EDITAR:

Algunas realmente buenas respuestas :) 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 funcione cada 10 segundos? System.Threading.Thread.Sleep (10000) simplemente hará que mi GUI se congele y mientras que el ciclo (verdadero) en Update () como se sugiere ofrece una excepción (Thread too busy).


Aquí hay un patrón de código fuente que puede usar. En este ejemplo, estoy redirigiendo la consola que luego uso para permitir que el trabajador de fondo escriba algunos mensajes en un cuadro de texto mientras se procesa.

Esta es la clase auxiliar TextBoxStreamWriter, que se usa para redirigir el resultado de la consola:

public class TextBoxStreamWriter : TextWriter { TextBox _output = null; public TextBoxStreamWriter(TextBox output) { _output = output; } public override void WriteLine(string value) { // When character data is written, append it to the text box. // using Invoke so it works in a different thread as well _output.Invoke((Action)(() => _output.AppendText(value+"/r/n"))); } }

Debe usarlo en el evento de carga de formulario de la siguiente manera:

private void Form1_Load(object sender, EventArgs e) { // Instantiate the writer and redirect the console out var _writer = new TextBoxStreamWriter(txtResult); Console.SetOut(_writer); }

También hay un botón en el formulario que inicia el trabajador de fondo, le pasa una ruta:

private void btnStart_Click(object sender, EventArgs e) { backgroundWorker1.RunWorkerAsync(txtPath.Text); }

Esta es la carga de trabajo del trabajador en segundo plano, tenga en cuenta cómo utiliza la consola para enviar mensajes al cuadro de texto:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { var selectedPath = e.Argument as string; Console.Out.WriteLine("Processing Path:"+selectedPath); // ... }

Si necesita restablecer algunos controles después, hágalo de la siguiente manera:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { progressBar1.Invoke((Action) (() => { progressBar1.MarqueeAnimationSpeed = 0; progressBar1.Style = ProgressBarStyle.Continuous; })); }

En este ejemplo, después de la finalización, se restablece una barra de progreso.

Importante: cuando acceda a un control de GUI, use Invocar como lo hice en los ejemplos anteriores. Usar Lambda lo hace fácil, como pudiste ver en el código.


Debería eliminar el tiempo (verdadero), está agregando controladores de eventos infinitos e invocándolos infinitas veces.


El enunciado if en la respuesta de @ Lee debería verse así:

bgw.RunWorkerCompleted += (o, e) => { if(e.Error == null && !e.Cancelled) { _userController.UpdateUsersOnMap(); } }

... si desea invocar UpdateUsersOnMap(); cuando no hay errores y BgWorker no se ha cancelado.


Necesita declarar y configurar el BackgroundWorker una vez, luego invocar el método RunWorkerAsync dentro de su ciclo ...

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


Puede usar el evento RunWorkerCompleted en la clase backgroundWorker para definir qué se debe hacer cuando la tarea en segundo plano se haya completado. Por lo tanto, debe hacer la llamada a la base de datos en el controlador DoWork y luego actualizar la interfaz en el manejador RunWorkerCompleted, algo como esto:

BackgroundWorker bgw = new BackgroundWorker(); bgw.DoWork += (o, e) => { longRunningTask(); } bgw.RunWorkerCompleted += (o, e) => { if(e.Error == null && !e.Cancelled) { _userController.UpdateUsersOnMap(); } } bgw.RunWorkerAsync();


Debe usar la propiedad Control.InvokeRequired para determinar si se encuentra en una cadena de fondo. Luego debe invocar su lógica que modificó su UI a través del método Control.Invoke para forzar las operaciones de su UI en el hilo principal. Para ello, crea un delegado y pásalo al método Control.Invoke . El truco aquí es que necesitas algún objeto derivado de Control para llamar a estos métodos.

Editar : como otro usuario publicó, si puede esperar al evento BackgroundWorker.Completed para actualizar su UI, entonces puede suscribirse a ese evento y llamar directamente a su código de UI. BackgroundWorker_Completed se invoca en el hilo principal de la aplicación. mi código asume que desea hacer actualizaciones durante la operación. Una alternativa a mi método es suscribirse al evento BwackgroundWorker.ProgressChanged , pero creo que necesitará llamar a Invoke para actualizar su UI 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() } } }