una - thread invoke c#
¿Cuándo usar el grupo de subprocesos en C#? (15)
He intentado aprender programación multiproceso en C # y estoy confundido acerca de cuándo es mejor usar un grupo de hilos versus crear mis propios hilos. Un libro recomienda usar un grupo de subprocesos solo para tareas pequeñas (lo que sea que eso signifique), pero parece que no puedo encontrar ninguna guía real. ¿Cuáles son algunas de las consideraciones que usa al tomar esta decisión de programación?
Aquí hay un buen resumen del grupo de subprocesos en .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx
La publicación también tiene algunos puntos sobre cuándo no deberías usar el grupo de subprocesos y comenzar tu propio subproceso.
El grupo de subprocesos está diseñado para reducir el cambio de contexto entre sus subprocesos. Considere un proceso que tiene varios componentes en ejecución. Cada uno de esos componentes podría estar creando subprocesos de trabajo. Cuantos más hilos haya en su proceso, más tiempo se desperdicia en el cambio de contexto.
Ahora, si cada uno de esos componentes pusiera en cola elementos en el grupo de subprocesos, tendría mucho menos sobrecarga de cambio de contexto.
El grupo de subprocesos está diseñado para maximizar el trabajo que se realiza en sus CPU (o núcleos de CPU). Es por eso que, de forma predeterminada, el grupo de subprocesos genera múltiples subprocesos por procesador.
Hay algunas situaciones en las que no le gustaría usar el grupo de subprocesos. Si está esperando la E / S o esperando un evento, etc., usted vincula ese hilo del grupo de subprocesos y no puede ser utilizado por nadie más. La misma idea se aplica a las tareas de larga ejecución, aunque lo que constituye una tarea larga es subjetiva.
Pax Diablo también tiene un buen punto. Spinning Up Threads no es gratis. Lleva tiempo y consumen memoria adicional para su espacio de pila. El grupo de subprocesos volverá a usar subprocesos para amortizar este costo.
Nota: usted preguntó sobre el uso de un hilo de grupo de subprocesos para descargar datos o realizar E / S de disco. No debe usar un hilo de grupo de subprocesos para esto (por las razones que describí anteriormente). En su lugar, use E / S asincrónicas (también conocidas como los métodos BeginXX y EndXX). Para un FileStream
que sería BeginRead
y EndRead
. Para una HttpWebRequest
que sería BeginGetResponse
y EndGetResponse
. Son más complicados de usar, pero son la forma correcta de realizar E / S de múltiples subprocesos.
La mayoría de las veces puede usar el grupo al evitar el costoso proceso de creación del hilo.
Sin embargo, en algunos casos es posible que desee crear un hilo. Por ejemplo, si usted no es el único que usa el grupo de subprocesos y el subproceso que crea es de larga duración (para evitar consumir recursos compartidos) o, por ejemplo, si desea controlar el tamaño de pila del subproceso.
No te olvides de investigar al trabajador de Antecedentes.
Encuentro en muchas situaciones, me da exactamente lo que quiero sin levantar objetos pesados.
Aclamaciones.
Para obtener el rendimiento más alto con unidades de ejecución simultáneas, escriba su propio grupo de subprocesos, donde se crea un conjunto de objetos de subproceso al inicio y vaya a bloqueo (anteriormente suspendido), esperando que se ejecute un contexto (un objeto con una interfaz estándar implementada por tu codigo).
Tantos artículos sobre Tareas vs. Hilos vs. .NET ThreadPool no logran darle realmente lo que necesita para tomar una decisión sobre el rendimiento. Pero cuando los compara, los hilos ganan y especialmente un grupo de hilos. Se distribuyen mejor entre las CPU y se inician más rápido.
Lo que debería discutirse es el hecho de que la unidad de ejecución principal de Windows (incluido Windows 10) es un hilo, y la sobrecarga de cambio de contexto del sistema operativo suele ser despreciable. En pocas palabras, no he podido encontrar evidencia convincente de muchos de estos artículos, ya sea que el artículo reclame un mayor rendimiento al guardar el cambio de contexto o un mejor uso de la CPU.
Ahora para un poco de realismo:
La mayoría de nosotros no necesita que nuestra aplicación sea determinista, y la mayoría de nosotros no tenemos un fondo difícil con hilos, que por ejemplo a menudo viene con el desarrollo de un sistema operativo. Lo que escribí arriba no es para principiantes.
Entonces, lo que puede ser más importante es discutir qué es fácil de programar.
Si crea su propio grupo de subprocesos, tendrá que escribir un poco, ya que tendrá que preocuparse por el seguimiento del estado de ejecución, cómo simular suspender y reanudar, y cómo cancelar la ejecución, incluso en una aplicación de ancho apagar. También podría tener que preocuparse de si desea aumentar dinámicamente su grupo y también qué limitación de capacidad tendrá su grupo. Puedo escribir un marco así en una hora, pero eso es porque lo he hecho muchas veces.
Quizás la forma más fácil de escribir una unidad de ejecución es usar una Tarea. La belleza de una Tarea es que puedes crear una y ponerla en línea en tu código (aunque puede justificarse la precaución). Puede pasar un token de cancelación para que lo maneje cuando desee cancelar la Tarea. Además, utiliza el enfoque prometedor para encadenar eventos y puede hacer que devuelva un tipo específico de valor. Además, con asincronización y espera, existen más opciones y su código será más portátil.
En esencia, es importante comprender los pros y los contras de Tareas vs. Hilos vs. .NET ThreadPool. Si necesito un alto rendimiento, voy a usar hilos, y prefiero usar mi propio grupo.
Una manera fácil de comparar es iniciar 512 subprocesos, 512 tareas y 512 subprocesos de subprocesos. Encontrará un retraso al principio con Threads (de ahí, por qué escribir un grupo de threads), pero todos los 512 Threads se ejecutarán en unos pocos segundos, mientras que las Tareas y los Threads de .NET ThreadPool tardan unos minutos en comenzar.
A continuación se muestran los resultados de una prueba de este tipo (i5 quad core con 16 GB de RAM), dando cada 30 segundos para ejecutarse. El código ejecutado realiza una E / S de archivo simple en una unidad SSD.
Quería un grupo de subprocesos para distribuir el trabajo entre los núcleos con la menor latencia posible, y eso no tenía que funcionar bien con otras aplicaciones. Descubrí que el rendimiento del grupo de subprocesos .NET no era tan bueno como podría ser. Sabía que quería un hilo por núcleo, así que escribí mi propia clase sustituta del conjunto de subprocesos. El código se proporciona como respuesta a otra pregunta de aquí .
En cuanto a la pregunta original, el grupo de subprocesos es útil para dividir los cálculos repetitivos en partes que se pueden ejecutar en paralelo (suponiendo que se puedan ejecutar en paralelo sin cambiar el resultado). La administración manual de subprocesos es útil para tareas como UI e IO.
Recomiendo leer este e-book gratuito: Enhebrado en C # por Joseph Albahari
Al menos lea la sección "Comenzando". El e-book ofrece una excelente introducción e incluye una gran cantidad de información avanzada sobre enhebrado.
Saber si usar o no el grupo de hilos es solo el comienzo. A continuación, deberá determinar qué método de ingreso al conjunto de subprocesos se adapta mejor a sus necesidades:
- Tarea Biblioteca paralela (.NET Framework 4.0)
- ThreadPool.QueueUserWorkItem
- Delegados asincrónicos
- BackgroundWorker
Este e-book explica todo esto y aconseja cuándo usarlos frente a crear su propio hilo.
Si tiene muchas tareas lógicas que requieren un procesamiento constante y desea que se hagan en paralelo, use el programador pool +.
Si necesita realizar sus tareas relacionadas con IO al mismo tiempo, como descargar cosas desde servidores remotos o acceso a disco, pero necesita hacer esto, diga una vez cada pocos minutos, luego cree sus propios hilos y elimínelos una vez que haya terminado.
Editar: Sobre algunas consideraciones, uso grupos de hilos para el acceso a la base de datos, física / simulación, AI (juegos), y para tareas con guiones ejecutados en máquinas virtuales que procesan muchas tareas definidas por el usuario.
Normalmente, un grupo consta de 2 subprocesos por procesador (tan probable 4 actualmente), sin embargo, puede configurar la cantidad de subprocesos que desee, si sabe cuántos necesita.
Editar: la razón para crear sus propios hilos es debido a cambios en el contexto (eso es cuando los hilos necesitan intercambiarse y retirarse del proceso, junto con su memoria). Tener cambios de contexto inútiles, por ejemplo, cuando no está utilizando sus hilos, simplemente dejándolos sentados como podría decirse, puede fácilmente la mitad del rendimiento de su programa (digamos que tiene 3 hilos que duermen y 2 hilos activos). Por lo tanto, si esos subprocesos de descarga simplemente están esperando, están consumiendo toneladas de CPU y enfriando la memoria caché para su aplicación real.
Si tiene una tarea en segundo plano que durará mucho tiempo, como durante toda la vida útil de su aplicación, entonces crear su propio hilo es algo razonable. Si tiene trabajos cortos que deben hacerse en un hilo, utilice la agrupación de hilos.
En una aplicación donde está creando muchos hilos, la sobrecarga de crear los hilos se vuelve sustancial. El uso del grupo de subprocesos crea los subprocesos una vez y los reutiliza, evitando así la sobrecarga de creación de subprocesos.
En una aplicación en la que trabajé, cambiar de crear subprocesos a usar el grupo de subprocesos para los subprocesos de corta duración realmente ayudó a poner en práctica la aplicación.
Sugeriría que uses un grupo de subprocesos en C # por las mismas razones que en cualquier otro idioma.
Cuando desee limitar el número de subprocesos en ejecución o no desee la sobrecarga de crearlos y destruirlos, use un grupo de subprocesos.
Mediante tareas pequeñas, el libro que lee significa tareas de corta duración. Si lleva diez segundos crear un hilo que solo se ejecuta durante un segundo, ese es un lugar donde deberías usar pools (ignora mis cifras reales, es la proporción lo que cuenta).
De lo contrario, pasas la mayor parte del tiempo creando y destruyendo hilos en lugar de simplemente hacer el trabajo que se supone que deben hacer.
Tenga cuidado con el grupo de subprocesos de .NET para las operaciones que pueden bloquear cualquier parte importante, variable o desconocida de su procesamiento, ya que es propenso a la falta de subprocesos. Considere usar las extensiones paralelas .NET, que proporcionan una buena cantidad de abstracciones lógicas sobre las operaciones con subprocesos. También incluyen un nuevo programador, que debería ser una mejora en ThreadPool. Mira here
Una razón para utilizar el grupo de subprocesos solo para tareas pequeñas es que hay un número limitado de subprocesos de grupo de subprocesos. Si uno se usa durante un tiempo prolongado, entonces evita que ese hilo sea utilizado por otro código. Si esto sucede muchas veces, el grupo de subprocesos puede agotarse.
Usar el grupo de subprocesos puede tener efectos sutiles: algunos temporizadores .NET usan subprocesos de grupo de subprocesos y no se activarán, por ejemplo.
Usualmente uso Threadpool cuando necesito hacer algo en otro hilo y realmente no me importa cuando se ejecuta o termina. Algo así como iniciar sesión o incluso descargar en segundo plano un archivo (aunque hay mejores formas de hacerlo asincrónico). Uso mi propio hilo cuando necesito más control. También lo que he descubierto es que usar una cola de Threadsafe (hackear la suya) para almacenar "objetos de comando" es agradable cuando tengo varios comandos en los que necesito trabajar en> 1 hilo. Por lo tanto, podría dividir un archivo Xml y poner cada elemento en una cola y luego tener varios hilos trabajando en hacer algún procesamiento en estos elementos. Escribí una cola así en la universidad (¡VB.net!) Que convertí a C #. Lo he incluido a continuación sin ningún motivo en particular (este código puede contener algunos errores).
using System.Collections.Generic;
using System.Threading;
namespace ThreadSafeQueue {
public class ThreadSafeQueue<T> {
private Queue<T> _queue;
public ThreadSafeQueue() {
_queue = new Queue<T>();
}
public void EnqueueSafe(T item) {
lock ( this ) {
_queue.Enqueue(item);
if ( _queue.Count >= 1 )
Monitor.Pulse(this);
}
}
public T DequeueSafe() {
lock ( this ) {
while ( _queue.Count <= 0 )
Monitor.Wait(this);
return this.DeEnqueueUnblock();
}
}
private T DeEnqueueUnblock() {
return _queue.Dequeue();
}
}
}
Utilice siempre un grupo de subprocesos si puede, trabaje con el mayor nivel de abstracción posible. Las piscinas de subprocesos se ocultan creando y destruyendo hilos para ti, ¡esto suele ser algo bueno!