NET define la funcionalidad de subprocesos múltiples en el espacio de nombres System.Threading. Por lo tanto, para utilizar subprocesos múltiples, primero debe declarar una referencia a este espacio de nombres (usando System.Threading;).
a. Iniciar un hilo. Como sugiere el nombre, "iniciar un hilo" significa crear e iniciar un hilo. El siguiente código puede lograr esto:
Hilo hilo1 = nuevo hilo (nuevo ThreadStart (Count));
Donde Count es la función que ejecutará el nuevo hilo.
b. Matar el hilo
"Matar un hilo" es erradicar un hilo. Para no desperdiciar sus esfuerzos, es mejor determinar si todavía está vivo (a través del atributo IsAlive) antes de matar un hilo y luego llamar al método Abort para matar el hilo. .
c. Pausar el hilo significa dejar que un hilo en ejecución duerma durante un período de tiempo. Por ejemplo, thread.Sleep(1000); es dejar que el hilo duerma durante 1 segundo.
d. La prioridad no necesita explicación. El atributo hreadPriority en la clase Thread se utiliza para establecer la prioridad, pero no hay garantía de que el sistema operativo acepte la prioridad. La prioridad de un hilo se puede dividir en 5 tipos: normal, superior a lo normal, inferior a lo normal, más alta y más baja. Los ejemplos de implementación específicos son los siguientes:
hilo.Prioridad = ThreadPriority.Highest;
e. suspender hilo
El método Suspend de la clase Thread se utiliza para suspender el hilo hasta que se llame a Resume antes de que el hilo pueda continuar ejecutándose. Si el hilo ya está suspendido, eso no funcionará.
si (thread.ThreadState = ThreadState.Running)
{
hilo.Suspend();
}
f. El hilo de recuperación se utiliza para reanudar el hilo suspendido para que pueda continuar ejecutándose. Si el hilo no está suspendido, no funcionará.
si (hilo.ThreadState = ThreadState.Suspended)
{
hilo.Reanudar();
}
A continuación se incluye un ejemplo para ilustrar la funcionalidad de subprocesamiento simple. Este ejemplo proviene de la documentación de ayuda.
usando Sistema;
usando System.Threading;
// Escenario de subprocesamiento simple: iniciar la ejecución de un método estático
// en un segundo hilo.
clase pública ThreadExample {
// El método ThreadProc se llama cuando se inicia el hilo.
// Se repite diez veces, escribe en la consola y produce
// el resto de su intervalo de tiempo cada vez y luego finaliza.
público estático vacío ThreadProc() {
para (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// Rinde el resto del intervalo de tiempo.
Hilo.Sleep(0);
}
}
público estático vacío principal() {
Console.WriteLine("Hilo principal: iniciar un segundo hilo.");
// El constructor de la clase Thread requiere ThreadStart
// delegado que representa el método a ejecutar en el
// thread.C# simplifica la creación de este delegado.
Hilo t = nuevo hilo (nuevo ThreadStart (ThreadProc));
// Inicie ThreadProc. En un monoprocesador, el hilo no obtiene
// cualquier tiempo de procesador hasta que el hilo principal ceda. Descomentar.
// el Thread.Sleep que sigue a t.Start() para ver la diferencia.
t.Inicio();
//Thread.Sleep(0);
para (int i = 0; i < 4; i++) {
Console.WriteLine("Subproceso principal: Trabajar un poco.");
Hilo.Sleep(0);
}
Console.WriteLine("Subproceso principal: Llame a Join(), para esperar hasta que finalice ThreadProc.");
t.Unirse();
Console.WriteLine("Subproceso principal: ThreadProc.Join ha regresado. Presione Entrar para finalizar el programa.");
Consola.ReadLine();
}
}
Este código produce un resultado similar al siguiente:
Hilo principal: iniciar un segundo hilo.
Hilo principal: Trabaja un poco.
ThreadProc: 0
Hilo principal: Trabaja un poco.
ThreadProc: 1
Hilo principal: Trabaja un poco.
Proceso de hilo: 2
Hilo principal: Trabaja un poco.
Proceso de hilo: 3
Hilo principal: Llame a Join(), para esperar hasta que finalice ThreadProc.
Proceso de hilo: 4
Proceso de hilo: 5
Proceso de hilo: 6
Proceso de hilo: 7
Proceso de hilo: 8
Proceso de hilo: 9
Hilo principal: ThreadProc.Join ha regresado. Presione Entrar para finalizar el programa.
En Visul C#, el espacio de nombres System.Threading proporciona algunas clases e interfaces que permiten la programación multiproceso. Hay tres métodos para crear subprocesos: Thread, ThreadPool y Timer. Aquí hay una breve introducción sobre cómo usarlos uno por uno.
1. Hilo
Este es quizás el método más complejo, pero proporciona una variedad de controles flexibles sobre los subprocesos. Primero, debe usar su constructor para crear una instancia de hilo. Sus parámetros son relativamente simples, con solo un delegado ThreadStart: public Thread(ThreadStart start) y luego llame a Start() para iniciarlo. Por supuesto, puede usar su atributo Prioridad. para establecer u obtener su prioridad de ejecución (enumeración ThreadPriority: normal, más baja, más alta, por debajo de lo normal, por encima de lo normal).
El siguiente ejemplo primero genera dos instancias de subprocesos t1 y t2, luego establece sus prioridades respectivamente y luego inicia los dos subprocesos (los dos subprocesos son básicamente iguales, excepto que su salida es diferente, t1 es "1" y t2 es "2). ", Según la proporción de la cantidad de caracteres que generan, podemos ver aproximadamente la proporción de tiempo de CPU que ocupan, que también refleja sus respectivas prioridades).
vacío estático principal (cadena [] argumentos)
{
Hilo t1 = nuevo hilo (nuevo hiloInicio(hilo1));
Hilo t2 = nuevo hilo (nuevo ThreadStart (Thread2));
t1.Prioridad = ThreadPriority.BelowNormal;
t2.Prioridad = ThreadPriority.Lowest;
t1.Inicio();
t2.Inicio();
}
Thread1 vacío estático público ()
{
para (int i = 1; i < 1000; i++)
{//Escribe un "1" cada vez que se ejecuta un bucle.
dosth();
Consola.Write("1");
}
}
Thread2 vacío estático público ()
{
para (int i = 0; i < 1000; i++)
{//Escribe un "2" cada vez que se ejecuta un bucle.
dosth();
Consola.Write("2");
}
}
dosth vacío estático público ()
{//Se utiliza para simular operaciones complejas
para (int j = 0; j < 10000000; j++)
{
int a = 15;
a = a*a*a*a;
}
}
El resultado de ejecutar el programa anterior es:
1111111111111111111111111111111111111111112111111111111111111111111111111111111111111112
1111111111111111111111111111111111111111112111111111111111111111111111111111111111111112
1111111111111111111111111111111111111111112111111111111111111111111111111111111111111112
De los resultados anteriores, podemos ver que el subproceso t1 consume mucho más tiempo de CPU que t2. Esto se debe a que la prioridad de t1 es mayor que la de t2. Si configuramos las prioridades de t1 y t2 en Normal, los resultados. son la siguiente imagen:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
En el ejemplo anterior, podemos ver que su estructura es similar a la del subproceso de trabajo de win32, pero es más simple. Solo necesita usar la función que llamará el subproceso como delegado y luego usar el delegado como parámetro. construir una instancia de hilo. Cuando se llama a Start() para comenzar, se llamará a la función correspondiente y la ejecución comenzará desde la primera línea de esa función.
A continuación, combinamos la propiedad ThreadState del hilo para comprender el control del hilo. ThreadState es un tipo de enumeración que refleja el estado del hilo. Cuando se crea una instancia de Thread por primera vez, su ThreadState no está iniciado; cuando el hilo se inicia llamando a Start (), su ThreadState se está ejecutando después de que se inicia el hilo. Si desea que se detenga (bloquee), puede llamar a Thread. Método Sleep(), tiene dos métodos sobrecargados (Sleep(int), Sleep(Timespan)), que son simplemente formatos diferentes para representar la cantidad de tiempo. Cuando se llama a esta función en un hilo, significa que el hilo se bloqueará. durante un período de tiempo (el tiempo está determinado por la cantidad de milisegundos o intervalo de tiempo pasado a Sleep, pero si el parámetro es 0, significa suspender este subproceso para permitir que otros subprocesos se ejecuten, especificando Infinite para bloquear el subproceso indefinidamente), en esta vez su ThreadState Se convertirá en WaitSleepJoin. Otra cosa que vale la pena señalar es que la función Sleep () está definida como estática. ! ¡Esto también significa que no se puede usar junto con una instancia de hilo, es decir, no hay una llamada similar a t1.Sleep(10)! De esta manera, la función Sleep () solo puede ser llamada por el subproceso que necesita "Dormir" y no puede ser llamada por otros subprocesos. Así como cuándo dormir es un asunto personal que otros no pueden decidir. . Pero cuando un hilo está en el estado WaitSleepJoin y tiene que reactivarlo, puede usar el método Thread.Interrupt, que generará una ThreadInterruptedException en el hilo. Veamos primero un ejemplo (tenga en cuenta el método de llamada de Sleep):
vacío estático principal (cadena [] argumentos)
{
Hilo t1 = nuevo hilo (nuevo hiloInicio(hilo1));
t1.Inicio();
t1.Interrupt();
E.EsperaUno();
t1.Interrupt();
t1.Unirse();
Console.WriteLine(“t1 es el final”);
}
AutoResetEvent estático E = nuevo AutoResetEvent (falso);
Thread1 vacío estático público ()
{
intentar
{//Se puede ver en los parámetros que provocará la hibernación.
Thread.Sleep(Tiempo de espera.Infinito);
}
captura (System.Threading.ThreadInterruptedException e)
{//Manejador de interrupciones
Console.WriteLine ("primera interrupción");
}
E.Establecer();
intentar
{// dormir
Thread.Sleep(Tiempo de espera.Infinito);
}
captura (System.Threading.ThreadInterruptedException e)
{
Console.WriteLine ("segunda interrupción");
}//Pausa durante 10 segundos
Hilo.Sueño (10000);
}
El resultado de la ejecución es: 1.ª interrupción
2da interrupción
(Después de 10s)t1 finaliza
En el ejemplo anterior, podemos ver que el método Thread.Interrupt puede despertar el programa desde un estado de bloqueo (WaitSleepJoin), ingresar al controlador de interrupción correspondiente y luego continuar con la ejecución (su ThreadState también cambia a En ejecución). La función debe tener en cuenta los siguientes puntos:
1. Este método no solo puede activar el bloqueo causado por Sleep, sino que también es efectivo para todos los métodos que pueden hacer que el hilo entre en el estado WaitSleepJoin (como Wait and Join). Como se muestra en el ejemplo anterior, al usarlo, debe colocar el método que causa el bloqueo del subproceso en el bloque try y colocar el controlador de interrupciones correspondiente en el bloque catch.
2. Llame a Interrupt en un subproceso determinado. Si está en el estado WaitSleepJoin, ingresará al controlador de interrupción correspondiente para su ejecución. Si no está en el estado WaitSleepJoin en este momento, se interrumpirá inmediatamente cuando ingrese a este estado más adelante. . Si se llama a Interrupt varias veces antes de interrumpir, solo la primera llamada es válida. Es por eso que utilicé la sincronización en el ejemplo anterior, para garantizar que la segunda llamada a Interrupt se llame después de la primera interrupción; de lo contrario, puede causar la segunda. La llamada no tiene efecto (si se llama antes de la primera interrupción). Puede intentar eliminar la sincronización. Es probable que el resultado sea: Primera interrupción.
El ejemplo anterior también utiliza otros dos métodos para hacer que el hilo entre en el estado WaitSleepJoin: usar el objeto de sincronización y el método Thread.Join. El uso del método Join es relativamente simple. Significa que el hilo actual que llama a este método se bloquea hasta que el otro hilo (t1 en este ejemplo) termina o pasa el tiempo especificado (si también toma un parámetro de tiempo). Si se produce una condición (si la hay), finaliza inmediatamente el estado WaitSleepJoin y entra en el estado En ejecución (la condición se puede determinar en función del valor de retorno del método .Join. Si es verdadero, el hilo finaliza; si es falso, se acabó el tiempo). El subproceso también se puede suspender utilizando el método Thread.Suspend. Cuando un subproceso está en estado En ejecución y se llama al método Suspend, ingresará al estado SuspendRequested, pero no se suspenderá inmediatamente hasta que el subproceso alcance un punto seguro. punto El hilo se cuelga, momento en el cual entrará en el estado Suspendido. Si se llama a un hilo que ya está suspendido, no será válido. Para reanudar la operación, simplemente llame a Thread.Resume.
Lo último de lo que hablamos es de la destrucción de subprocesos. Podemos llamar al método Abort en el subproceso que necesita ser destruido y generará una ThreadAbortException en este subproceso. Podemos poner algo de código en el hilo en el bloque try y poner el código de procesamiento correspondiente en el bloque catch correspondiente. Cuando el hilo está ejecutando el código en el bloque try, si se llama a Abortar, saltará al bloque catch correspondiente. Ejecutado dentro del bloque catch, terminará después de ejecutar el código en el bloque catch (si ResetAbort se ejecuta dentro del bloque catch, será diferente: cancelará la solicitud de Abortar actual y continuará ejecutándose hacia abajo. Por lo tanto, si Si desea asegurarse de que un hilo termine, es mejor utilizar Join , como en el ejemplo anterior).
2. Grupo de subprocesos
Thread Pool (ThreadPool) es un método relativamente simple. Es adecuado para tareas cortas que requieren múltiples subprocesos (como algunos subprocesos que a menudo están bloqueados). Su desventaja es que no puede controlar los subprocesos creados ni se puede establecer su prioridad. Dado que cada proceso tiene solo un grupo de subprocesos y, por supuesto, cada dominio de aplicación tiene solo un grupo de subprocesos (línea), encontrará que las funciones miembro de la clase ThreadPool son todas estáticas. Cuando llama a ThreadPool.QueueUserWorkItem, ThreadPool.RegisterWaitForSingleObject, etc. por primera vez, se creará una instancia de grupo de subprocesos. La siguiente es una introducción a las dos funciones en el grupo de subprocesos:
public static bool QueueUserWorkItem( //Devuelve verdadero si la llamada es exitosa
WaitCallback callBack, // El delegado llamado por el hilo que se creará
estado del objeto //Parámetros pasados al delegado
) // Su otra función sobrecargada es similar, excepto que el delegado no toma parámetros. La función de esta función es poner en cola el subproceso que se creará en el grupo de subprocesos cuando el número de subprocesos disponibles en el grupo de subprocesos no es cero. el grupo de subprocesos ha creado subprocesos. Si el número es limitado (el valor predeterminado es 25), se creará este subproceso. De lo contrario, se pondrá en cola en el grupo de subprocesos y esperará hasta que tenga subprocesos disponibles.
público estático RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject, // WaitHandle para registrar
WaitOrTimerCallback callBack, // Delegado de llamada de subproceso
estado del objeto, // Parámetros pasados al delegado
int TimeOut,//Tiempo de espera, la unidad es milisegundos,
bool ejecutarOnlyOnce file:// Si se debe ejecutar solo una vez
);
delegado público vacío WaitOrTimerCallback (
estado del objeto, // es decir, los parámetros pasados al delegado
bool timedOut//true significa debido a una llamada de tiempo de espera; de lo contrario, significa waitObject
);
La función de esta función es crear un hilo de espera. Una vez que se llama a esta función, este hilo se creará. Estará en un estado "bloqueado" hasta que el parámetro waitObject cambie al estado terminado o llegue el tiempo establecido. Vale la pena señalar que este "Bloqueo" es muy diferente del estado WaitSleepJoin del subproceso: cuando un subproceso está en el estado WaitSleepJoin, la CPU lo activará periódicamente para sondear y actualizar la información de estado, y luego ingresará al estado WaitSleepJoin nuevamente. El cambio de subproceso consume muchos recursos; y el subproceso creado con esta función es diferente. La CPU no cambiará a este subproceso antes de que se active para ejecutarse. No consume tiempo de la CPU ni desperdicia tiempo de cambio de subproceso. ¿Sabes cuándo ejecutarlo? De hecho, el grupo de subprocesos generará algunos subprocesos auxiliares para monitorear estas condiciones de activación. Una vez que se cumplan las condiciones, se iniciarán los subprocesos correspondientes. Por supuesto, estos subprocesos auxiliares también tomarán tiempo, pero si necesita crear más espera. subprocesos, utilice las ventajas del grupo de subprocesos se vuelven más obvias. Vea el ejemplo a continuación:
AutoResetEvent estático ev = nuevo AutoResetEvent (falso);
público estático int principal (cadena [] argumentos)
{ ThreadPool.RegisterWaitForSingleObject(
ev,
nuevo WaitOrTimerCallback (WaitThreadFunc),
4,
2000,
false // Indica que el temporizador se restablecerá cada vez que se complete la operación de espera hasta cerrar sesión y esperar.
);
ThreadPool.QueueUserWorkItem (nuevo WaitCallback (ThreadFunc),8);
Hilo.Sueño (10000);
devolver 0;
}
ThreadFunc vacío estático público (objeto b)
{ Console.WriteLine ("el objeto es {0}",b);
para(int i=0;i<2;i++)
{ Hilo.Sueño (1000);
ev.Set();
}
}
vacío estático público WaitThreadFunc (objeto b, bool t)
{ Console.WriteLine ("el objeto es {0},t es {1}",b,t);
}
El resultado de su operación es:
el objeto es 8
el objeto es 4,t es falso
el objeto es 4,t es falso
el objeto es 4,t es verdadero
el objeto es 4,t es verdadero
el objeto es 4,t es verdadero
De los resultados anteriores, podemos ver que el hilo ThreadFunc se ejecutó una vez y WaitThreadFunc se ejecutó 5 veces. Podemos juzgar el motivo para iniciar este hilo a partir del parámetro bool t en WaitOrTimerCallback: si t es falso, significa waitObject; de lo contrario, se debe al tiempo de espera. Además, también podemos pasar algunos parámetros al hilo a través del objeto b.
3. Temporizador
Es adecuado para métodos que deben llamarse periódicamente. No se ejecuta en el hilo que creó el temporizador. Se ejecuta en un hilo separado asignado automáticamente por el sistema. Esto es similar al método SetTimer en Win32. Su estructura es:
Temporizador público (
Devolución de llamada TimerCallback, // Método a llamar
estado del objeto, // parámetros pasados a la devolución de llamada
int dueTime,//¿Cuánto tiempo llevará comenzar a llamar a la devolución de llamada?
int period // El intervalo de tiempo para llamar a este método
); // Si dueTime es 0, la devolución de llamada ejecuta su primera llamada inmediatamente. Si dueTime es Infinito, la devolución de llamada no llama a su método. El temporizador está deshabilitado, pero se puede volver a habilitar usando el método Cambiar. Si el período es 0 o Infinito y dueTime no es Infinito, la devolución de llamada llama a su método una vez. El comportamiento periódico del temporizador está deshabilitado, pero se puede volver a habilitar usando el método Cambiar. Si el periodo es cero (0) o Infinito, y dueTime no es Infinito, la devolución de llamada llama a su método una vez. El comportamiento periódico del temporizador está deshabilitado, pero se puede volver a habilitar usando el método Cambiar.
Si queremos cambiar el período y la hora de vencimiento del temporizador después de crearlo, podemos cambiarlo llamando al método Cambiar del temporizador:
cambio de bolo público (
hora de vencimiento int,
periodo entero
);//Obviamente los dos parámetros cambiados corresponden a los dos parámetros en Timer
público estático int principal (cadena [] argumentos)
{
Console.WriteLine ("el período es 1000");
Temporizador tm = nuevo temporizador (nuevo TimerCallback (TimerCall), 3,1000,1000);
Hilo. Sueño (2000);
Console.WriteLine ("el período es 500");
tm.Cambio (0,800);
Hilo.Sueño (3000);
devolver 0;
}
TimerCall vacío estático público (objeto b)
{
Console.WriteLine ("timercallback; b es {0}",b);
}
El resultado de su operación es:
el periodo es 1000
devolución de llamada del temporizador; b es 3
devolución de llamada del temporizador; b es 3
el periodo es 500
devolución de llamada del temporizador; b es 3
devolución de llamada del temporizador; b es 3
devolución de llamada del temporizador; b es 3
devolución de llamada del temporizador; b es 3
Resumir
De la breve introducción anterior, podemos ver las ocasiones en las que se usan respectivamente: Thread es adecuado para ocasiones que requieren un control complejo de subprocesos; ThreadPool es adecuado para tareas cortas que requieren múltiples subprocesos (como algunos que a menudo están bloqueados). ); Timer es adecuado para métodos que deben llamarse periódicamente. Siempre que comprendamos sus características de uso, podremos elegir bien el método adecuado.