C# Multihilo

HiloSe define como la ruta de ejecución de un programa. Cada hilo define un flujo de control único. Si su aplicación implica operaciones complejas y que consumen tiempo, a menudo es beneficioso configurar diferentes rutas de ejecución de hilos, cada hilo ejecuta un trabajo específico.

Un hilo es unproceso ligero. Un ejemplo común de uso de hilos es la implementación de programación paralela en los sistemas operativos modernos. El uso de hilos ahorra el desperdicio de ciclos de CPU y mejora la eficiencia de la aplicación.

Hasta ahora, los programas que hemos escrito son procesos únicos que se ejecutan como una instancia de ejecución de la aplicación con un solo hilo. Sin embargo, de esta manera la aplicación solo puede ejecutar una tarea a la vez. Para ejecutar varias tareas simultáneamente, puede dividirse en hilos más pequeños.

Ciclo de vida de un hilo

El ciclo de vida de un hilo comienza cuando se crea un objeto de la clase System.Threading.Thread y termina cuando el hilo se termina o completa su ejecución.

A continuación se enumeran los distintos estados del ciclo de vida de un hilo:

  • Estado no iniciado: Es la situación cuando se crea la instancia del hilo pero no se ha llamado al método Start.
  • Estado listo: Es la situación cuando el hilo está listo para ejecutarse y espera un ciclo de CPU.
  • Estado no ejecutable: El hilo no es ejecutable en las siguientes situaciones:
    • Se ha llamado al método Sleep
    • Se ha llamado al método Wait
    • Bloqueado por operaciones de E/S
  • Estado finalizado: Es la situación cuando el hilo ha completado su ejecución o ha sido abortado.

Hilo principal

En C#,System.Threading.Threadla clase se utiliza para el trabajo con hilos. Permite crear y acceder a hilos individuales en una aplicación multiproceso. El primer hilo que se ejecuta en un proceso se denominahilo principal。

Cuando un programa C# comienza a ejecutarse, el hilo principal se crea automáticamente. Los hilos creados con laThreadclase se denominan subprocesos del hilo principal. Puede utilizar laCurrentThreadpropiedad para acceder al hilo.

El siguiente programa demuestra la ejecución del hilo principal:

Ejemplo

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class MainThreadProgram
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "MainThread";
            Console.WriteLine("This is {0}", th.Name);
            Console.ReadKey();
        }
    }
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

This is MainThread

Propiedades y métodos comunes de la clase Thread

La siguiente tabla enumeraThreadalgunas propiedades comunes de la clasePropiedades:

PropiedadDescripción
CurrentContextObtiene el contexto actual en el que se está ejecutando el hilo.
CurrentCultureObtiene o establece la referencia cultural del hilo actual.
CurrentPrincipalObtiene o establece el responsable actual del hilo (para la seguridad basada en roles).
CurrentThreadObtiene el hilo que se está ejecutando actualmente.
CurrentUICultureObtiene o establece la referencia cultural actual utilizada por el administrador de recursos para buscar recursos específicos de una referencia cultural en tiempo de ejecución.
ExecutionContextObtiene un objeto ExecutionContext que contiene información sobre varios contextos del hilo actual.
IsAliveObtiene un valor que indica el estado de ejecución del hilo actual.
IsBackgroundObtiene o establece un valor que indica si un hilo es un hilo en segundo plano.
IsThreadPoolThreadObtiene un valor que indica si el hilo pertenece al grupo de hilos administrados.
ManagedThreadIdObtiene el identificador único del hilo administrado actual.
NameObtiene o establece el nombre del hilo.
PriorityObtiene o establece un valor que indica la prioridad de programación del hilo.
ThreadStateObtiene un valor que contiene el estado del hilo actual.

La siguiente tabla enumeraThreadalgunos métodos comunes de la claseMétodos:

N.ºNombre del método y descripción
1public void Abort()
Produce ThreadAbortException en el hilo que llama a este método, para iniciar el proceso de terminación de este hilo. Llamar a este método generalmente termina el hilo.
2public static LocalDataStoreSlot AllocateDataSlot()
Asigna espacios de datos sin nombre en todos los hilos. Para obtener un mejor rendimiento, utilice campos marcados con el atributo ThreadStaticAttribute.
3public static LocalDataStoreSlot AllocateNamedDataSlot( string name)
Asigna espacios de datos con nombre en todos los hilos. Para obtener un mejor rendimiento, utilice campos marcados con el atributo ThreadStaticAttribute.
4public static void BeginCriticalRegion()
Notifica al host que la ejecución va a entrar en una región de código donde los efectos de una anulación de hilo o una excepción no controlada pueden afectar a otras tareas en el dominio de la aplicación.
5public static void BeginThreadAffinity()
Notifica al host que el código administrado va a ejecutar instrucciones que dependen de la identidad del subproceso del sistema operativo físico actual.
6public static void EndCriticalRegion()
Notifica al host que la ejecución va a entrar en una región de código donde la anulación de hilo o las excepciones no controladas solo afectan a la tarea actual.
7public static void EndThreadAffinity()
Notifica al host que el código administrado ha terminado de ejecutar instrucciones que dependen de la identidad del subproceso del sistema operativo físico actual.
8public static void FreeNamedDataSlot(string name)
Elimina la asociación entre nombres y ranuras para todos los hilos del proceso. Para obtener un mejor rendimiento, utilice campos marcados con el atributo ThreadStaticAttribute.
9public static Object GetData( LocalDataStoreSlot slot )
Recupera un valor de la ranura especificada en el hilo actual, en el dominio actual del hilo. Para obtener un mejor rendimiento, utilice campos marcados con el atributo ThreadStaticAttribute.
10public static AppDomain GetDomain()
Devuelve el dominio actual en el que se está ejecutando el hilo actual.
11public static AppDomain GetDomainID()
Devuelve un identificador único de dominio de aplicación.
12public static LocalDataStoreSlot GetNamedDataSlot( string name )
Busca una ranura de datos con nombre. Para obtener un mejor rendimiento, utilice campos marcados con el atributo ThreadStaticAttribute.
13public void Interrupt()
Interrumpe un hilo que está en el estado WaitSleepJoin.
14public void Join()
Bloquea el subproceso que llama hasta que un subproceso termine, mientras continúa ejecutando el procesamiento estándar de bombeo de mensajes COM y SendMessage. Este método tiene diferentes sobrecargas.
15public static void MemoryBarrier()
Sincroniza el acceso a la memoria de la siguiente manera: el procesador que ejecuta el subproceso actual, al reordenar las instrucciones, no puede ejecutar primero los accesos a memoria posteriores a la llamada a MemoryBarrier y luego los accesos a memoria anteriores a la llamada a MemoryBarrier.
16public static void ResetAbort()
Cancela la solicitud de Abort para el subproceso actual.
17public static void SetData( LocalDataStoreSlot slot, Object data )
Establece datos en la ranura especificada en el subproceso que se está ejecutando actualmente para el dominio actual de ese subproceso. Para obtener un mejor rendimiento, utilice campos marcados con el atributo ThreadStaticAttribute.
18public void Start()
Inicia un subproceso.
19public static void Sleep( int millisecondsTimeout )
Suspende el hilo durante un período de tiempo.
20public static void SpinWait( int iterations )
Hace que el hilo espere la cantidad de tiempo definida por el parámetro iterations.
21public static byte VolatileRead( ref byte address )
public static double VolatileRead( ref double address )
public static int VolatileRead( ref int address )
public static Object VolatileRead( ref Object address )

Lee el valor de un campo. Independientemente del número de procesadores o del estado de la caché del procesador, este valor es el último escrito por cualquier procesador de la computadora. Este método tiene diferentes sobrecargas. Aquí solo se muestran algunas.
22public static void VolatileWrite( ref byte address, byte value )
public static void VolatileWrite( ref double address, double value )
public static void VolatileWrite( ref int address, int value )
public static void VolatileWrite( ref Object address, Object value )

Escribe inmediatamente un valor en un campo para que sea visible para todos los procesadores de la computadora. Este método tiene diferentes sobrecargas. Aquí solo se muestran algunas.
23public static bool Yield()
Hace que el hilo que llama ejecute otro hilo que está listo para ejecutarse en el procesador actual. El sistema operativo selecciona el hilo a ejecutar.

Crear hilos

Los hilos se crean extendiendo la clase Thread. La clase Thread extendida llama alStart()método para iniciar la ejecución del subproceso secundario.

El siguiente programa demuestra este concepto:

Ejemplo

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

In Main: Creating the Child thread
Child thread starts

Gestionar hilos

La clase Thread proporciona varios métodos para gestionar hilos.

El siguiente ejemplo demuestra elsleep()uso del método, que se utiliza para suspender un hilo durante un tiempo específico.

Ejemplo

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
            // El hilo se suspende durante 5000 milisegundos
            int sleepfor = 5000;
            Console.WriteLine("Child Thread Paused for {0} seconds",
                              sleepfor / 1000);
            Thread.Sleep(sleepfor);
            Console.WriteLine("Child thread resumes");
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

Destruir hilos

Abort()El método se utiliza para destruir hilos.

Al lanzar,threadabortexceptionse aborta el hilo en tiempo de ejecución. Esta excepción no puede capturarse; si hay unfinallybloque, el control se transfiere alfinallybloque.

El siguiente programa ilustra esto:

Ejemplo

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            try
            {

                Console.WriteLine("Child thread starts");
                // Cuenta hasta 10
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine("Child Thread Completed");

            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine("Thread Abort Exception");
            }
            finally
            {
                Console.WriteLine("Couldn't catch the Thread Exception");
            }

        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
            // Detiene el hilo principal durante un tiempo
            Thread.Sleep(2000);
            // Ahora se aborta el subproceso secundario
            Console.WriteLine("In Main: Aborting the Child thread");
            childThread.Abort();
            Console.ReadKey();
        }
    }
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception 
Otras extensiones