varias - C#: ¿Tengo que deshacerme de un BackgroundWorker creado en tiempo de ejecución?
que es backgroundworker c# (8)
¿Por qué no envolver en una declaración de uso? No mucho esfuerzo extra y obtienes la eliminación:
private void PerformLongRunningOperation()
{
using (BackgroundWorker worker = new BackgroundWorker())
{
worker.DoWork += delegate
{
// perform long running operation here
};
worker.RunWorkerAsync();
}
}
EDITAR:
Ok, hice una pequeña prueba para ver qué está pasando con la eliminación y otras cosas:
using System;
using System.ComponentModel;
using System.Threading;
namespace BackgroundWorkerTest
{
internal class Program
{
private static BackgroundWorker _privateWorker;
private static void Main()
{
PrintThread("Main");
_privateWorker = new BackgroundWorker();
_privateWorker.DoWork += WorkerDoWork;
_privateWorker.RunWorkerCompleted += WorkerRunWorkerCompleted;
_privateWorker.Disposed += WorkerDisposed;
_privateWorker.RunWorkerAsync();
_privateWorker.Dispose();
_privateWorker = null;
using (var BW = new BackgroundWorker())
{
BW.DoWork += delegate
{
Thread.Sleep(2000);
PrintThread("Using Worker Working");
};
BW.Disposed += delegate { PrintThread("Using Worker Disposed"); };
BW.RunWorkerCompleted += delegate { PrintThread("Using Worker Completed"); };
BW.RunWorkerAsync();
}
Console.ReadLine();
}
private static void WorkerDisposed(object sender, EventArgs e)
{
PrintThread("Private Worker Disposed");
}
private static void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
PrintThread("Private Worker Completed");
}
private static void WorkerDoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
PrintThread("Private Worker Working");
}
private static void PrintThread(string caller)
{
Console.WriteLine("{0} Thread: {1}", caller, Thread.CurrentThread.ManagedThreadId);
}
}
}
Aquí está el resultado:
Main Thread: 1
Private Worker Disposed Thread: 1
Using Worker Disposed Thread: 1
Private Worker Working Thread: 3
Using Worker Working Thread: 4
Using Worker Completed Thread: 4
Private Worker Completed Thread: 3
A partir de algunas pruebas, parece que Dispose () básicamente no tiene ningún efecto en un BackgroundWorker iniciado. Ya sea que lo llame dentro del alcance de una declaración de uso, o lo use declarado en el código y lo elimine de inmediato y lo desreferencia, todavía se ejecuta normalmente. El Evento Dispuesto se produce en el hilo principal, y DoWork y RunWorkerCompleted se producen en los hilos del grupo de subprocesos (el que esté disponible cuando se desencadena el evento). Intenté un caso en el que cancelé el registro del evento RunWorkerCompleted justo después de llamar a Dispose (antes de que DoWork tuviera la oportunidad de completarlo) y RunWorkerCompleted no se activó. Esto me lleva a creer que todavía puede manipular el objeto BackgroundWorker a pesar de que se lo haya eliminado.
Entonces, como otros han mencionado, en este momento, parece que llamar Dispose no es realmente necesario. Sin embargo, tampoco veo ningún daño al hacerlo, al menos por mi experiencia y estas pruebas.
Normalmente tengo un código como este en un formulario:
private void PerformLongRunningOperation()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
// perform long running operation here
};
worker.RunWorkerAsync();
}
Esto significa que no elimino BackgroundWorker
, mientras que si lo hubiera agregado el diseñador de formularios, entonces creo que se eliminaría.
¿Esto causará algún problema? ¿Es más correcto declarar un _saveWorker
nivel de _saveWorker
, y luego llamar a Dispose
en él desde el método de disposición del formulario?
Call dispose en su evento RunWorkerCompleted
.
BackgroundWorker wkr = new BackgroundWorker();
wkr.DoWork += (s, e) => {
// Do long running task.
};
wkr.RunWorkerCompleted += (s, e) => {
try {
if (e.Error != null) {
// Handle failure.
}
} finally {
// Use wkr outer instead of casting.
wkr.Dispose();
}
};
wkr.RunWorkerAsync();
El intento adicional / finalmente es asegurar que se llame a Dispose
si su código de finalización genera una excepción.
El desafío es asegurarse de que solo deseche el BackgroundWorker
vez que haya terminado de ejecutarse. No puede hacer eso en el evento Completed
, porque ese evento lo plantea el propio BackgroundWorker.
BackgroundWorker está destinado a ser utilizado como un componente en un formulario de WinForms, por lo que recomendaría hacerlo o cambiar a algo como Thread.QueueUserWorkItem
. Esto usará un hilo de grupo de hilos, y no requerirá ninguna limpieza especial cuando haya terminado.
El manejador de finalización se ejecuta en el subproceso original (es decir, no en el subproceso de fondo del grupo de subprocesos). Los resultados de tus pruebas realmente confirman esa premisa.
En mi opinión, en general, si es IDisposable, debería ser Dispose () d cuando haya terminado con él. Incluso si técnicamente no es necesario deshacerse de la implementación actual de BackgroundWorker, no querrá sorprenderse con implementaciones internas posteriores que sí lo hagan.
No me molestaría, el único recurso al que Bgw podría aferrarse es el hilo y si no tienes un ciclo infinito en tu delegado, entonces estás bien.
BackgroundWorker hereda IDisposable()
de Component pero realmente no lo necesita.
Compáralo con empujar tu método directamente en ThreadPool. No descarta (no puede) los hilos, ciertamente no los del Pool.
Pero si su muestra está completa en el sentido de que no está utilizando las funciones de evento Completado o Progreso / Cancelación, también podría usar ThreadPool.QueueUserWorkItem()
.
Sí, debe deshacerse del trabajador de segundo plano.
Puede que le resulte más fácil utilizar ThreadPool.QueueUserWorkItem(...)
que no requiere ninguna limpieza posterior.
Detalles adicionales sobre por qué siempre debe llamar a Dispose ():
Aunque si observas la clase BackgroundWorker, en realidad no realiza ninguna limpieza de subprocesos en su método de eliminación, sigue siendo importante llamar a Dispose debido al efecto que la clase tiene en el recolector de elementos no utilizados.
Las clases con finalizadores no son GCed inmediatamente. Se guardan y se agregan a la cola del finalizador. A continuación, se ejecuta el hilo del finalizador (que después de las llamadas al patrón estándar dispone). Esto significa que el objeto sobrevivirá en la generación 1 de GC. Y las colecciones de gen 1 son mucho más raras que las colecciones de gen 0, por lo que los objetos se quedan en la memoria mucho más tiempo.
Sin embargo, si llama a Dispose (), el objeto no se agregará a la cola de finalización, por lo que se puede recolectar de forma gratuita.
No es realmente un gran problema, pero si está creando muchos de ellos terminará usando más memoria de la necesaria. Realmente debería considerarse una buena práctica llamar siempre a deshacerse de los objetos que tienen un método de eliminación.
Entonces supongo que, en general, no es un requisito 100% difícil y rápido. Su aplicación no explotará (o incluso perderá memoria) si no llama a Dispose (), pero en algunas circunstancias puede tener efectos negativos. El trabajador en segundo plano se diseñó para usar como un componente de WinForms, así que úselo de esa manera, si tiene requisitos diferentes y no desea usarlo como un componente de WinForms, no lo use, use la herramienta correcta para el trabajo, como el ThreadPool.
Se considera una mejor práctica llamar a Dispose () para todos los objetos IDisposables. Eso les permite liberar recursos no administrados que pueden estar reteniendo, como Handles. Las clases IDisposable también deben tener finalizadores, cuya presencia puede retrasar el momento en que el GC puede recopilar completamente esos objetos.
Si su clase asigna un IDisposable y lo asigna a una variable miembro, entonces, en general, también debería ser IDisposable.
Sin embargo, si no llama a Dispose (), finalmente se llamará al Finalizer y los recursos se limpiarán finalmente. La ventaja de llamarlo explícitamente es que esas cosas pueden suceder más rápidamente y con menos sobrecarga, lo que mejora el rendimiento de la aplicación y reduce la presión de la memoria.