• En programación hablamos de concurrencia cuando se ejecuta más de una tarea al
mismo tiempo. • Esta habilidad es útil cuando necesitamos que una aplicación haga alguna cosa mientras está trabajando en algo más. • La concurrencia es un aspecto clave de las aplicaciones modernas, permite que: • Los usuarios finales puedan interactuar con la interfaz de usuario de manera no-bloqueante. • Un servidor pueda atender varias peticiones en simultáneo y no afectar los tiempos de respuesta ante períodos de alta demanda. • Realizar tareas de cómputo complejas de manera más rápida y haciendo un uso más eficaz los recursos de la computadora. • La programación multi-hilo <multithreaded programming> y programación asincrónica <asynchronous programming> son las dos formas de concurrencia más comunes. Secuencial vs Concurrente • Los programas secuenciales ejecutan una operación detrás de otra (en secuencia), aunque el orden puede variar (if, while), solo hacen una sola cosa a la vez, es decir solo hay un flujo de ejecución. • Un programa concurrente hace varias cosas al mismo tiempo, varias actividades que progresan en paralelo, estos programas tienen varios flujos de ejecución (threads), cada uno de ellos ejecuta una secuencia de operaciones. ¿Cómo se da la concurrencia? • En un sistema con varios procesadores se puede ejecutar una tarea en cada procesador, esto es ejecución simultánea (paralelismo físico).
• En un monoprocesador se intercalan las operaciones de las tareas, esto es
multiplexado en tiempo (paralelismo lógico), o multicore si tiene varios cores.
• La máquina virtual (o el sistema operativo) se encarga de los detalles, desde
un punto de vista lógico son equivalentes. ¿Para qué se usa? • Interfaces de usuario reactivas • Atención a sucesos asíncronos • Gestión de ventanas, widgets, etc. • Servidores • Atención a múltiples clientes • Gestión de protocolos de comunicación • Mejoras de prestaciones • Ejecución en multiprocesadores • Cálculos complejos • Ejecución de algoritmos complejos en paralelo Hilos vs Procesos • Los procesos del sistema se ejecutan en paralelo en la computadora y se mantienen separados unos de otros y comparten la memoria del sistema. • Los hilos (threads) se ejecutan en paralelo en un proceso, y comparten memoria del proceso que los contiene • El Thread Scheduler es quien se encarga de asignar tiempos de ejecución y que los hilos no se queden colgados. Programación multi-hilo • Un proceso es un programa en ejecución que tiene asignados recursos tales como memoria e hilos. • Un hilo (thread), también llamado hebra o subproceso, es la unidad básica a la que un sistema operativo asigna tiempo de procesamiento. Son los encargados de ejecutar nuestro código sentencia a sentencia. • Por defecto cada proceso tiene un único hilo, es decir, sólo puede procesar una tarea a la vez. La programación multi-hilo (multithreaded programming) permite que un proceso se ejecute sobre múltiples hilos y cada uno de esos hilos esté realizando una tarea distinta en paralelo. • Todos los hilos de un mismo proceso comparten los mismos recursos asignados por el sistema operativo. Multitarea apropiativa • Por cada núcleo de la CPU, se puede ejecutar a lo mucho un proceso en cada momento. Windows y otros sistemas operativos modernos simulan la ejecución paralela de tareas dividiendo el tiempo de procesamiento entre los hilos, permitiendo que se vayan ejecutando uno después de otro en pequeñas fracciones de tiempo. El hilo que se está ejecutando es suspendido cuando termina su fracción de tiempo, luego el procesador permite que otro hilo se ejecute por el mismo periodo de tiempo. Esta forma de simular el paralelismo recibe el nombre de multitarea apropiativa (preemptive multitasking). • Cuando Windows cambia de un hilo a otro, guarda el contexto donde se ejecutó el hilo actual y recarga el contexto del próximo hilo en la cola de ejecución. Hay que ser conscientes de que esto también consume tiempo y recursos. • Cada hilo tiene una pila de ejecución (call stack) independiente, esto significa que cada uno maneja su propia secuencia de funciones a ejecutar. • En algunos tipos de aplicación existen hilos especiales, por ejemplo un hilo para la interfaz de usuario (UI Thread) o el hilo principal en los programas de consola (Main Thread). • Todas las aplicaciones de .NET tienen un conjunto de hilos (thread pool) que se encarga de mantener un número de hilos activos esperando para ejecutar cualquier trabajo que se requiera. Lo podemos ver como un lugar donde podemos poner en cola tareas a realizar y que se ajustará automáticamente de acuerdo a la demanda. • En .NET se solía utilizar la clase Thread para trabajar con hilos, la cual es una abstracción de bajo nivel. El thread pool es una abstracción de un nivel un poco más alto, ya que se encargará por si mismo de instanciar un hilo si existe la necesidad. Actualmente no se recomienda crear instancias de Thread ya que existen nuevas soluciones que fueron afinadas para cubrir de forma eficiente y sencilla la gran mayoría de los escenarios reales. • Las clases con las que trabajaremos son abstracciones de alto nivel que ponen en cola trabajo para que sea resuelto por el thread pool. Programación asíncrona • La programación multi-thread implica el reparto de tareas (thread) de una misma aplicación, de forma que se realicen de manera independiente unas de otras. De este modo se consigue un uso más óptimo del tiempo del procesador. Las tareas no se realizan en realidad de forma paralela. El procesador asigna un tiempo de procesamiento a cada tarea en función de su importancia. Este cambio de contexto implica que el procesador memoriza la pila de la tarea actual antes de restaurar la de la tarea a la que le da paso. • C# 5 introduce de nuevo palabras reservadas (await y async) para facilitar el desarrollo asíncrono. El desarrollo síncrono implica que una función que se invoca bloquea la ejecución del programa hasta que aquella termina. Cuando una función se invoca de forma asíncrona, la ejecución del programa principal continúa. Por tanto, existe una noción de ejecución en paralelo y de concurrencia, como ocurre con la programación multi-thread. La clase Thread • El espacio de nombres System.Threading alberga clases que permiten crear y controlar las tareas, principalmente mediante la clase Thread. Los threads se deben usar cuando una aplicación debe gestionar varias tareas independientes, como gestionar una interfaz de usuario o realizar un tratamiento de datos. La aplicación tendrá mejor rendimiento si estas dos tareas se desarrollan en threads específicos. • El delegado ThreadStart se emplea para crear un nuevo thread. Su constructor toma como argumento la declaración del método que se ejecutará en este nuevo thread: • ThreadStart newThread = new ThreadStart(OtherThread); • El delegado se pasa a continuación como argumento del constructor del objeto Thread: • Thread thread = new Thread(newThread); • A continuación, se llama al método Start Funciones asíncronas • Las funciones asíncronas permiten mejorar la reactividad de una aplicación. Es frecuente tener que efectuar, en una aplicación, procesamientos relativamente largos. Con un modelo de desarrollo clásico, la aplicación completa se bloquea esperando a que termine el procesamiento. Esta situación es molesta para el usuario. No sabe, realmente, qué está haciendo la aplicación. Si quiere detener la aplicación durante este tiempo de bloqueo, no tiene más opción que utilizar el administrador de tareas de Windows. • Para evitar esta situación, a partir de la versión 5 del lenguaje C# es posible definir funciones asíncronas. La palabra reservada async incluida en la firma de una función hace que su ejecución se produzca de forma asíncrona. Para que este mecanismo sea realmente eficaz hace falta, además, indicar en el interior de este tipo de función al menos una ubicación donde pueda suspenderse la ejecución y esperar a que el procesamiento termine. La palabra reservada await situada delante de una expresión indica estos puntos de interrupción. Cuando termina el procesamiento, se evalúa la expresión y se retoma la ejecución de la función. Para que este mecanismo funcione, es preciso que la expresión genere un tipo Task<...>. Asincronía en C#
Método ASYNC AWAIT - TASK
• Un método async quiere estar • Un método asíncrono tiene que en sincronía con métodos devolver un objeto del tipo Task asíncronos • Await le indica al método async • ¿Qué significa estar en sintonía que debe esperar a un método con un método asíncrono? asíncrono que devuelve un Task. • Suspenderse y esperar a que un Cuando el método se completa, proceso asíncrono que él mismo await se encarga de obtener el ha iniciado se termine para luego resultado de dicho método. reanudarse Programación en paralelo • La programación en paralelo (parallel programming) es un tipo de programación multi-hilo, que a su vez es una forma de concurrencia. Se utiliza cuando se necesita dividir una gran carga de trabajo computacional en partes independientes y ejecutarlas en paralelo, maximizando el uso de los núcleos de la CPU. • La programación en paralelo nos sirve para dividir una tarea en distintas partes y trabajar esas partes de manera simultánea. • Beneficio: Ahorrar tiempo • No es recomendable usar paralelismo en ambientes web: ASP.NET y ASP.NET Core • Librería de Tareas en Paralelo • PLINQ (LINQ en paralelo) • Paralelismo de Datos • Paralelismo de Tareas • No siempre debemos usar paralelismo