2010-02-03 15 views
12

Soy relativamente nuevo en la programación de .NET y el multihilo en general, y me preguntaba si está bien usar .NET proporcionado BackgroundWorker para engendrar subprocesos de trabajo para hacer algo de trabajo en una aplicación de consola ? De la documentación variada en línea, veo que la intención para esta clase es más para aplicaciones orientadas a la interfaz de usuario, donde desea hacer algo de trabajo en segundo plano, pero mantener la UI receptiva e informar progreso, cancelar el procesamiento si es necesario, etc.Utilizando la clase .NET BackgroundWorker en la aplicación de consola

En mi caso, básicamente tengo una clase de controlador en la que deseo generar múltiples subprocesos de trabajo para hacer algo de procesamiento (lo que limita el número máximo de subprocesos de trabajo generados mediante un semáforo). Entonces quiero que mi clase de controlador se bloquee hasta que todos los hilos hayan completado el procesamiento. Entonces, después de comenzar un hilo de trabajo para hacer algo de trabajo, quiero que el hilo pueda notificar el hilo del controlador cuando se complete el procesamiento. Veo que puedo usar la clase de trabajador en segundo plano, y manejar los eventos DoWork y RunWorkerCompleted para lograr esto, sin embargo, me preguntaba si esta es una buena idea. ¿Hay mejores formas de lograr esto?

+2

Aunque puede que no lo ayude con este problema, las nuevas características de programación paralela de .NET Framework 4 pueden interesarle. Algo para vigilar: se lanzará pronto. http://msdn.microsoft.com/en-us/concurrency/default.aspx –

Respuesta

8

Esto no funcionará en una aplicación de consola, ya que SynchronizationContext.Current nunca se inicializará. Esto es inicializado por Windows Forms o WPF para usted, cuando está usando una aplicación GUI.

Dicho esto, no hay ninguna razón para hacer esto. Simplemente use ThreadPool.QueueUserWorkItem, y un evento de reinicio (ManualResetEvent o AutoResetEvent) para atrapar el estado de finalización y bloquear el hilo principal.


Editar:

Después de ver algunos de los comentarios OP, pensé que me gustaría añadir esto.

La alternativa "más bonito", en mi opinión, sería la de obtener una copia de la Rx Framework, ya que incluye un backport del TPL en .NET 4. Esto permitirá utilizar la sobrecarga de Parallel.ForEach que proporciona la opción de suministrar una instancia de ParallelOptions. Esto le permitirá restringir el número total de operaciones simultáneas, y manejar todo el trabajo por usted:

// using collection of work items, such as: List<Action<object>> workItems; 
var options = new ParallelOptions(); 
options.MaxDegreeOfParallelism = 10; // Restrict to 10 threads, not recommended! 

// Perform all actions in the list, in parallel 
Parallel.ForEach(workItems, options, item => { item(null); }); 

Sin embargo, el uso de Parallel.ForEach, yo personalmente dejar que el sistema maneje el grado de paralelismo. Asignará automáticamente un número apropiado de hilos (especialmente cuando/si esto se mueve a .NET 4).

9

Si su requisito es sólo para bloquear hasta que todos los hilos han terminado, eso es muy fácil - simplemente iniciar nuevos temas y luego llamar Thread.Join en cada una de ellas:

using System; 
using System.Collections.Generic; 
using System.Threading; 

public class Test 
{ 
    static void Main() 
    { 
     var threads = new List<Thread>(); 
     for (int i = 0; i < 10; i++) 
     { 
      int copy = i; 
      Thread thread = new Thread(() => DoWork(copy)); 
      thread.Start(); 
      threads.Add(thread); 
     } 

     Console.WriteLine("Main thread blocking"); 
     foreach (Thread thread in threads) 
     { 
      thread.Join(); 
     } 
     Console.WriteLine("Main thread finished"); 
    } 

    static void DoWork(int thread) 
    { 
     Console.WriteLine("Thread {0} doing work", thread); 
     Random rng = new Random(thread); // Seed with unique numbers 
     Thread.Sleep(rng.Next(2000)); 
     Console.WriteLine("Thread {0} done", thread); 
    } 
} 

EDIT: Si tiene acceso a. NET 4.0, entonces el TPL es definitivamente el camino correcto a seguir. De lo contrario, sugeriría usar una cola de productor/consumidor (hay un montón de código de muestra). Básicamente, tienes una cola de elementos de trabajo y tantos hilos de consumo como núcleos (suponiendo que estén vinculados a la CPU, querrás adaptarlo a tu carga de trabajo). Cada hilo de consumidor tomaría elementos de la cola y los procesaría, uno a la vez. Exactamente cómo lo gestiones dependerá de tu situación, pero no es terriblemente complicado. Es incluso más fácil si puede comenzar con todo el trabajo que necesita hacer para empezar, para que los hilos puedan salir cuando encuentren que la cola está vacía.

+0

Sin embargo, esto agrega bastante tiempo de inicio al uso de subprocesos ThreadPool. Iniciar su propio hilo no es rápido, y probablemente sea innecesario. –

+1

@Reed: a menos que el grupo de subprocesos ya esté calentado, ese costo estará allí de todos modos ... y es realmente * no * tan costoso para iniciar subprocesos. En mi ** netbook ** toma menos de 1 segundo para iniciar y unir 1000 hilos. No lo considero como "bastante tiempo de inicio", y es poco probable que el OP realmente necesite 1000 hilos de todos modos. Considero esto como una solución más simple que usar el grupo de hilos. –

+0

Jon, pero con thread.join, solo estoy bloqueando el hilo principal hasta que termine. Sin embargo, ¿qué sucede si deseo recibir una notificación cuando el hilo específico se complete, para poder realizar alguna operación adicional en el hilo principal? – jvtech

1

Tienes razón en que el trabajo en segundo plano no funcionará aquí. De hecho, está diseñado para trabajar con entornos GUI (WinForms y WPF por igual) donde el hilo principal está permanentemente ocupado comprobando/ejecutando Message Pump (que procesa todos los eventos de Windows como Click y Resize, más los que provienen de backgroundworker). Sin la cola de eventos, el hilo principal se agotará. Y sin la cola de eventos, BackgroundWorker tampoco puede invocar las devoluciones de llamada en el subproceso principal.

De hecho, hacer Multithreading en una consola es una docena de veces más difícil porque no tiene el Message Pump para resolver los dos grandes problemas: cómo mantener el hilo principal vivo y cómo informar el hilo principal de cambios necesarios en la interfaz de usuario . La Programación basada en eventos es el campo de aprendizaje ideal para Multithreading.

hay algunas soluciones:

la expansión de su aplicación de consola con un bombeo de mensajes. Dado que es poco más que una colección de rosca compartido de Delegados (donde otros procesos únicamente Agregar material) y un bucle while es sorprendentemente fácil: C# Console App + Event Handling

Las clases ThreadPool e hilo. Han existido tanto tiempo como el BackgroundWorker (desde .NET 2.0) y parecen estar diseñados para resolver el problema de Console. Aún necesitas bloquear tu hilo principal con un bucle. ThreadPool también limitará el Número de subprocesos a algo apto para aplicaciones en la máquina Real. Debería evitar los límites fijos de subprocesos y, en su lugar, confiar en las características ThreadPooling del sistema operativo para manejar la pregunta de "¿cuántos subprocesos se deberían ejecutar a la vez?". Por ejemplo, un número fijo de 5 subprocesos podría ser ideal para su máquina de desarrollo principal de 6. Pero sería demasiado para una computadora de escritorio de núcleo único. Y sería un Bottleneck inútil en un servidor que tiene fácilmente entre 16 y 64 núcleos con GiB de RAM de sobra.

Si tiene .NET 4.0 o posterior, vale la pena ver el Paralelismo de tarea recién agregado. http://msdn.microsoft.com/en-us/library/dd537609.aspx Ha construido en ThreadPooling, limitadas capacidades de priorización y una cadena de latas y une varias tareas fácilmente (todo lo que Thread podría hacer, Tarea puede hacer). Usar la tarea también habilita el uso de asincrónico y espera Palabras clave: http://msdn.microsoft.com/en-us/library/hh191443.aspx Nuevamente, no se puede evitar bloquear el hilo principal en una aplicación de consola.

+0

En realidad, un evento es muy fácil de usar y una forma muy efectiva de mantener un programa de consola ejecutándose hasta que algo específico ocurra. – user34660

Cuestiones relacionadas