safe - windows forms c# thread
Actualizar la interfaz de usuario de varios hilos de trabajo(.NET) (6)
Tengo un proyecto mascota en el que estoy trabajando que tiene múltiples hilos de trabajo. Resulta difícil seguir enviando todo a la consola, por lo que quiero desarrollar una UI que tenga un área de salida por hilo. Quiero saber la mejor manera para que los hilos envíen actualizaciones a la IU. Tengo dos ideas:
1) Haga que cada subproceso establezca un indicador "DataUpdated" cuando haya nuevos datos disponibles y haga que la UI compruebe periódicamente si hay nuevos datos.
2) Cree cada subproceso con una devolución de llamada a un método de actualización de UI (...) para que se llame cuando haya nuevos datos disponibles.
Actualmente me estoy inclinando por (2) por dos razones: no me gusta la idea de "verificar" cada tema, y porque esta es mi primera aplicación multiproceso y (2) parece más simple de lo que probablemente sea. Quiero saber:
- ¿Qué opción es preferible en términos de simplicidad y eficiencia?
- ¿Tiene algún consejo para implementar (2) o algo similar (es decir, más orientado a eventos)?
En la mayoría de los casos, lo más fácil sería usar el componente BackgroundWorker como se sugiere en la respuesta de itowlson, y si es posible, le sugiero que use ese enfoque. Si, por alguna razón, no puede usar un componente BackgroundWorker para su propósito, como por ejemplo si está desarrollando con .Net 1.1 (¡ay!) O con un marco compacto, entonces podría necesitar usar un enfoque alternativo:
Con los controles de Winform, debe evitar modificar los controles en cualquier hilo que no sea el hilo que creó originalmente el control. El componente BackgroundWorker maneja esto para usted, pero si no lo está utilizando, puede y debe usar la propiedad InvokeRequired y el método Invoke que se encuentran en la clase System.Windows.Forms.Control. A continuación se muestra un ejemplo que usa esta propiedad y método:
public partial class MultithreadingForm : Form
{
public MultithreadingForm()
{
InitializeComponent();
}
// a simple button event handler that starts a worker thread
private void btnDoWork_Click(object sender, EventArgs e)
{
Thread t = new Thread(WorkerMethod);
t.Start();
}
private void ReportProgress(string message)
{
// check whether or not the current thread is the main UI thread
// if not, InvokeRequired will be true
if (this.InvokeRequired)
{
// create a delegate pointing back to this same function
// the Invoke method will cause the delegate to be invoked on the main UI thread
this.Invoke(new Action<string>(ReportProgress), message);
}
else
{
// txtOutput is a UI control, therefore it must be updated by the main UI thread
if (string.IsNullOrEmpty(this.txtOutput.Text))
this.txtOutput.Text = message;
else
this.txtOutput.Text += "/r/n" + message;
}
}
// a generic method that does work and reports progress
private void WorkerMethod()
{
// step 1
// ...
ReportProgress("Step 1 completed");
// step 2
// ...
ReportProgress("Step 2 completed");
// step 3
// ...
ReportProgress("Step 3 completed");
}
}
La forma preferida de implementar multihilo en su aplicación es usar el componente BackgroundWorker . El componente BackgroundWorker usa un modelo basado en eventos para multihilo. El subproceso de trabajador ejecuta su controlador de eventos DoWork y el subproceso que crea los controles ejecuta los controladores de evento ProgressChanged y RunWorkerCompleted.
Cuando actualiza los controles de su UI en el controlador de eventos ProgressChanged, estos se actualizan automáticamente en el hilo principal, lo que evitará que obtenga excepciones de cruce cruzado.
Busque aquí un ejemplo sobre cómo usar el backgroundworker.
Puede hacer que sus subprocesos de trabajo generen eventos y que la secuencia de interfaz de usuario principal agregue controladores de eventos. Debe tener cuidado de no generar demasiados eventos, ya que podría ponerse peor si sus subprocesos de trabajo generan varios eventos por segundo.
Este artículo ofrece una descripción general rápida.
Puede implementar fácilmente (2) creando componentes de BackgroundWorker y haciendo el trabajo en sus manejadores de DoWork:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += /* your background work here */;
bw.ProgressChanged += /* your UI update method here */;
bw.RunWorkerAsync();
Cada BackgroundWorker puede informar el progreso al subproceso de la interfaz de usuario llamando a ReportProgress: aunque esto está diseñado principalmente para informar el progreso en un proceso limitado, eso no es obligatorio; también puede pasar sus propios datos personalizados si así lo requiere su actualización de UI. Llamarías a ReportProgress desde tu controlador DoWork.
Lo bueno de BackgroundWorker es que se ocupa de muchos detalles complicados de cross-threading. También se ajusta al modelo de actualizaciones impulsado por eventos que usted (con razón) prefiere a las devoluciones de llamada explícitas.
Voto también el # 2 pero con BackgroundWorkers en lugar de System.Threading.Threads.
Si está creando sus propios subprocesos (no hilos de BackgroundWorker o ThreadPool) puede pasar un método de devolución de llamada desde su subproceso principal al que se llama desde el subproceso de trabajador. Esto también le permite pasar argumentos a la devolución de llamada e incluso devolver un valor (como un indicador de ir / no seguir). En su devolución de llamada, actualiza la UI a través del Dispatcher del control objetivo:
public void UpdateUI(object arg)
{
controlToUpdate.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal
, new System.Windows.Threading.DispatcherOperationCallback(delegate
{
controToUpdate.property = arg;
return null;
}), null);
}
}