Material de la charla de Computación paralela en Windows de la CodeMotion

Como viene siendo habitual aquí tenéis el material de la charla sobre computación paralela del pasado sábado día 24 de marzo.

clip_image002

El código de ejemplo lo podéis descargar de aquí: http://bit.ly/TPLCodeMotion

Y ya sabéis nada de dejar los try/catch vacíos.
¡Espero que disfrutéis de todos los cores del mundo!

Saludos. Luis.

Lanzamiento de Visual Studio 2010 – La importancia de la concurrencia

Hola a todos!

Dentro de poco empieza la gira de lanzamiento de Visual Studio 2010 por España y en este Lanzamiento tendré el honor de participar con una charla sobre la importancia de la computación paralela, así que espero veros a todos el día 13 de Abril en Barcelona y el 20 de Abril en Madrid.

Mientras podéis ir echando un vistazo al material sobre concurrencia de una charla anterior aquí.

Saludos!

Luis Guerrero.

TPL – Cancelacion de Task

¡Hola a todos! Seguimos con los post sobre Task Parallel Library de .NET Framework 4, en este artículo vamos a ver cuál es el soporte de cancelación de Task que tenemos en TPL.

Como hemos comentado en el artículo anterior las Task son la unidad mínima de ejecución de TPL, incluso PLINQ (Parallel LINQ) utiliza Task internamente para sus operaciones. También hemos visto como las Task tienen alguna similitud con los Worker Threads del ThreadPool de .NET.

Cuando nosotros en código lanzamos un Worker Thread con el método ThreadPool.QueueWorkItem, estamos encolando un trabajo para que se ejecute en otro Thread diferente, pero no tenemos control en como este Worker thread se ejecuta, de hecho no podemos saber cuándo se ha completado, no podemos cancelarlo y no podemos devolver un resultado de esa ejecución asíncrona.

Esto ahora con las Task cambia, ahora a través de la propiedad Status podemos saber en qué estado está nuestra Task, la propiedad Result nos dará el resultado de la ejecución de la Task. Pero, ¿Qué hay de la cancelación?.

Cancelar un Task no es una tarea trivial, porque implica muchas decisiones y puede conllevar muchos errores. Si pensamos en cómo se cancela un Thread, el CLR lo que hace es inyectar un ThreadAbortException en la ejecución del thread haciendo que este se aborte, pero lo que no podemos controlar es en qué punto de la ejecución del thread se va a producir esto, y lo más peligroso de eso es que podemos estar dentro de un bloque de lock y la inyectarse la excepción puede pasar que nunca se salga de ese bloque de lock, haciendo que pueda pasar un deadlock en nuestro código. Este es solo un ejemplo de lo que puede pasar pero por supuesto puede ser mucho más complicado.

¿Cómo se puede cancelar una Tarea de manera segura?

Hay dos opciones posibles, si la tarea esta creada y se ha llamado al método Start y la Task está esperando para ejecutarse (Status=WaitToRun) y se cancela TPL automáticamente cancelará la tarea por nosotros (Status=Cancelled). Pero si la tarea está ejecutándose o esperando a un evento externo se realiza de otra manera.

Para poder ver esta segunda parte tenemos que comprender primero como se gestiona esto en código para entender cuál es el mecanismo que TPL nos proporciona para cancelar Task.

Task t = new Task((index) =>
{
    Thread.SpinWait(1000);
    if (!cancellationSource.IsCancellationRequested)
    {
        Thread.Sleep(r.Next(0, 1000));
    }
}, i, cancellationSource.Token);
list.Add(t);

Si nos fijamos en este ejemplo de código al crear una instancia de la clase Task ahora pasamos por parámetro un CancellationToken que nos va a permitir comunicarnos con el sistema de cancelación de TPL. Ese CancellationToken viene de una estructura llamada CancellationTokenSource que es realmente la que gestiona la comunicación de la cancelación de las Task.

Lo primero que hacemos es generar una lista con todas las Task que vamos a ejecutar, con eso lo que tenemos es instancias de la clase Task, pero todavía no se están ejecutando, es después cuando llamamos al método Start de todos los objetos Task a la vez.

Como el TaskScheduler predeterminado es el ThreadPool de .NET al principio en una carga de trabajo tan grande 10000 Task, el ThreadPool se tendrá que tunear a sí mismo para empezar a crear Workers Threads y aumentar dinámicamente la capacidad de ejecución por nosotros. Esta es una de las cosas más cómodas de TPL, porque nosotros no tememos que directamente estar pendiente de la heurística del ThreadPool para saber cuál es el número de Threads ideal para nuestro sistema.

Además de todo esto tenemos un método que nos permite cancelar todas las Task a la vez.

public void CancelTask()
{
    cancellationSource.Cancel();
}

Simplemente lo que hacemos es llamar a Cancel del objeto CancellationTokenRequest y este se comunicará con la infraestructura de TPL para empezar la cancelación. Como dijimos antes la cancelación de tareas que están es estado WaitingToRun es automática, pero las que se están ejecutando es más complicado.

Si nos fijamos en el ejemplo de arriba, en el cuerpo del método que se ejecutará por cada Task, hay una sentencia condicional if !cancellationSource.IsCancellationRequested en la que estamos comprobando que no se ha realizado una solicitud de cancelación de tareas. Esto es así porque es la única manera segura y organizada de cancelar un Task. Este ejemplo no es muy representativo para el soporte de cancelación, pero por ejemplo si en esta Task se estuvieran generando por pasos un informe que tarda mucho tiempo, generar el reporte, consulta a la base de datos, ect, podemos hacer comprobaciones en cada paso de que se ha solicitado una cancelación de la Task y así de manera segura cancelar la petición y liberar todos los recursos generados por ese reporte.

Para poder sacar todo el partido de la cancelación en nuestras Task, tenemos que trabajar activamente con TPL para que la experiencia de cancelación sea la más adecuada a cada situación, habrá pasos que no se puedan cancelar hasta un determinado punto, en cualquier caso TPL nunca abortará la ejecución de ese Task por nosotros, pues como he comentado antes esto puede conllevar muchos problemas.

image

Otro de los escenarios posibles cuando se trabaja con cancelación es que de alguna manera nos gustaría saber cuándo un CancellationToken se ha cancelado, para eso tenemos varias opciones, porque el propio CancellationToken tiene un método para que registremos un delegado de tipo Action que será llamado cuando se cancele el CancellationToken. También existe la opción de registrar un objeto de tipo ICancelableOperation, que es la interfaz que implementan el tipo CancellationTokenSource para poder enlazar así objetos de cancelación y crear dependencias entre ellos.

Aquí os podéis descargar el código de ejemplo de la aplicación de WPF.

http://www.luisguerrero.net/downloads/TaskCancel.zip

Saludos. Luis.

TPL – Task

Como comentamos en el anterior artículo las Task son las unidades básicas de ejecución dentro de TPL (Task Parallel Library) y en este doble artículo vamos a ver cuáles son las posibilidades que tenemos para trabajar con las Task dentro de nuestro código.

Una de las misiones de la TPL es ofrece una API consistente para el trabajo concurrente de software, es decir para tareas que se van a ejecutar de manera concurrente en un sistema con más de un procesador. Como bien es sabido la unidad mínima que el SO es cada de enviar para ejecutar es un Thread, pero nosotros aquí estamos hablando de Task. ¿Qué relación hay entre un Thread y un Task?.

Un thread es una unidad mínima y demasiado concreta para ejecutar código de manera concurrente. Dentro de .NET se puede crear una instancia de la clase Thread para ejecutar un método que nosotros queramos dentro de un thread diferente, pero una vez que ese método se ejecuta el Thread termina y se liberan los recursos utilizados por él. Es por eso que utilizar una estructura un poco más de alto nivel nos ayuda a abstraernos de cómo nuestras Task se ejecutan.

Ahora bien esto no quiere decir que las Task *no* se e ejecutan dentro de un thread, sino que también se ha creado una estructura intermedia llamada TaskScheduler que nos permite definir cómo se van a ejecutar nuestras Task. La igualdad con respecto al par Schedule/Thread es muy parecida pues tenemos conceptos muy similares, pero como veremos más adelante las Task son mucho más flexibles que un Thread y permite un sinfín de configuraciones, es más permiten que se ejecuten de manera síncrona, cosa que un thread no puede.

Basic – Creación de una Task.

Para crear una instancia de la clase Task podemos hacerla de varias maneras, aquí tenemos algunos ejemplos.

Task t = new Task(() =>
{
    Console.WriteLine("hola desde un task");
    Thread.Sleep(1000 * 4);
});

Task argumento = new Task(index =>
{
    int value = (int)index;
    for (int i = 0; i < value; i++)
    {
        DoStuff();
    }
    Thread.Sleep(1000 * 4);
}, 90);

En ambos ejemplo se han utilizado Lambdas para crear los delegados que ejecutarán el código pero se puede utilizar el delegado Action y Action<T> para sacar ese valor a un método externo.

Con esto simplemente lo que hemos hecho es definir únicamente el objeto Task y ahora mismo esta simplemente creado pero no se le ha especificado que se tiene que ejecutar. Para ejecutarlo tenemos dos opciones, de manera síncrona y de manera asíncrona (concurrente). Puede parecer algo raro el tener el soporte de ejecución síncrona el algo que está pensado para ejecutarse de manera concurrente siempre, pero es que en algunos casos es útil y así podemos definir todo nuestro trabajo con Task y luego decidir cómo queremos ejecutar.

t.RunSynchronously();            

t.Start();    

En el método Start tenemos una sobrecarga que nos permite especificar cuál es el TaskScheduler en el que queremos ejecutar nuestra Task. Si no especificamos ninguno el sistema automáticamente utiliza un TaskScheduler que utiliza internamente el ThreadPool de Windows para ejecutar el código. Por supuesto podemos definir y crear nuestros propios TaskScheduler simplemente heredando de la clase TaskScheduler. Además del que está definido con el ThreadPool si estamos dentro de una aplicación de UI como Windows Forms o WPF estos disponen de un TaskScheduler propio para ejecutar tareas dentro del bucle de mensajes de la aplicación.

Para poder acceder a este TaskScheduler tenemos que llamar a este código:

TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();

Además de estas opciones tenemos algunas otras con las que podemos trabajar con Task. Podemos esperar la ejecución de una Task concreta un tiempo determinado o simplemente podemos esperar infinitamente hasta que la tarea termine.

Una de las sobrecargas del método wait acepta un objeto de tipo CancellationToken que como su nombre indica es un objeto que nos ayuda a tener un soporte de cancelación de Task que veremos más adelante.

También disponemos de varias propiedades dentro de la clase Task que nos dan información del estado de la tarea como:

  • State: enumerado que nos indica en qué estado está la Task, como: Created, WaitToRun, Running, RanToCompletion, ect.
  • AsynState: objeto de usuario.
  • Exception: si se ha producido una excepción durante la ejecución de la Task y no se ha controlado el estado de la Task será Faulted y en esta propiedad aparecerá un objeto de tipo AggregateException que contiene una lista de todas las excepciones que se han producido durante la ejecución de la Task.
  • Result: obtiene de manera segura el resultado de la ejecución de la Task.

Como última opción veremos que tenemos un método llamado ContinueWith que nos permite ejecutar la Task especificada justo después de que esta termine pudiendo así enlazar Task y crear dependencias entre ellas. Esto es muy útil cuando se trabaja con funciones que van a tardar mucho en ejecutarse como una lectura de un fichero, una query en una base de datos o una actividad en background.

Con lo que dijimos antes vamos a ver un ejemplo utilizando TaskScheduler.FromCurrentSynchronizationContext() en una aplicación WPF para ver ContinuwWith.

Como bien es sabido en WPF y en Windows Forms no es posible actualizar el estado de un objeto de UI desde el Thread que no es el Thread que creo el objeto, es decir si estamos ejecutando código desde otro Thread, algo normal con las Task, no vamos a poder actualizar el resultado de nuestra Task, así que tenemos que sincronizar el acceso.

Task.Factory.StartNew(() =>
{
    // simulando una operacion lenta

    Thread.Sleep(1000 * 2);
    return "hola";

}).ContinueWith(task =>
{
    result.Text = task.Result;

}, TaskScheduler.FromCurrentSynchronizationContext());

Aquí está el disponible el código de ejemplo.

http://www.luisguerrero.net/downloads/task101.zip

 

Saludos. Luis.

Task Parallel Library – Introduccion

Una de las nuevas novedades que .NET Framework 4.0 incluye es el la Task Parallel Library una serie de APIS nuevas para la programación multihilo. La idea principal de esta librería, que viene incluida en el propio framework, es que cuando tengamos que añadir paralelismo y concurrencia a nuestras aplicaciones sea de lo más sencillo.

Actualmente los procesadores ya no incrementan la velocidad en Gigahercios sino que lo que hacen es replicar el hardware haciendo que nos encontremos dentro del mismo encapsulado FPGA dos procesadores exactamente iguales con sus caches de segundo y primer nivel. Esto cambia la manera de desarrollar software porque ya no nos encontramos con procesadores más rápidos sino con procesadores con más cores, 2,4,6,8 ect.

Con este nuevo escenario tenemos los desarrolladores tenemos que empezar a paralelizar nuestro software para explotar toda la potencia de los procesadores.

Una de las cosas buenas de la TPL, es que si la maquina donde se va a ejecutar tiene 2, 4 o 8 procesadores, la TPL es capaz de escalar sin necesidad de recompilar ni configurar, es decir es capaz de usar todos los cores disponibles.

Eso quiere decir que cuanto más cores utilicemos más velocidad ganaremos, aunque esto no es siempre así, porque no todas las tareas son sensibles de ser paralelizadas. Además hay que tener en cuenta que usar la TPL añade complejidad en la ejecución de la aplicación y esto en algunas ocasiones puede hacer que se degrade el rendimiento y no lo aumente. Hablaremos de eso más adelante.

Aquí tenemos la primera toma de contacto con la TPL:

Parallel.For(startIndex, endIndex, (currentIndex) => DoSomeWork(currentIndex));

Parallel.For nos permite ejecutar de manera concurrente el cuerpo de un bucle for, haciendo que la ejecución se propague por todos los cores disponibles en el sistema. Así de sencillo.

Pero como hemos comentado antes no esto a veces aumenta el rendimiento y en otros lo degrada. Veamos porque.

Cuando realizamos un bucle normal todo el código se ejecuta de manera secuencial, es decir una instrucción detrás de otra, pero cuando estamos realizando una paralelizacion de nuestro código tenemos varios threads que están ejecutando código en el mismo instante de tiempo. Si nos ponemos a pensar cómo se podría implementar a mano un Parallel.For, lo primero que tendríamos que hacer es realizar una partición de las iteraciones para dependiendo de los procesadores que tengamos repartir el trabajo.

Si tenemos por ejemplo que recorrer una lista de 50000 elementos y tenemos 2 procesadores, podemos partir la lista de dos sublistas de 25000 y generar dos threas que se encarguen de recorrer esos elementos. Ponemos los threads a ejecutar y tenemos que sincronizar cuando los dos threads terminan.

Básicamente esto es de lo que se encarga el Parallel.For de hacer por nosotros, de una manera cómoda y elegante.

Ahora bien, ¿Cuándo no es recomendable realizar un Parallel.For?, hay una regla que funciona en la mayoría de los casos, cuando el tiempo de ejecución del cuerpo del for sea mayor o igual que el tiempo de creación de los threas y de la sincronización, también es lo mismo si tenemos colecciones pequeñas. ¿Qué quieres decir esto?, veamos un ejemplo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ParalleFor
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program();
        }

        private List<int> GetRandomList()
        {
            List<int> list = new List<int>();
            for (int i = 0; i < 90000000; i++)
            {
                list.Add(i);
            }
            return list;
        }

        public Program()
        {
            List<int> list = GetRandomList();

            Stopwatch st = new Stopwatch();

            // sum all
            decimal value = 2;
            st.Start();
            for (int i = 0; i < list.Count; i++)
            {
                decimal final = value * list[i];
            }
            st.Stop();
            Console.WriteLine(st.Elapsed);
            st.Reset();

            st.Start();
            Parallel.For(0, list.Count, index =>
            {
                decimal final = value * list[(int)index];
            });
            st.Stop();
            Console.WriteLine(st.Elapsed);
        }
    }
}

Si nos fijamos la lista de con la que trabajamos es realmente grande, eso significa que si paralelizamos el bucle for ganaremos en rendimiento como muestra la salida de la ejecución. Pero si tenemos una lista pequeña no ganaremos tiempo sino que perderemos porque tenemos que sincronizar los n threas para esperar a que todos terminen, además del tiempo necesario para inicializarlos.

Otro de los grandes problemas de la paralelizacion es que necesitamos estructuras para sincronizar nuestro código y tener claro los conceptos de: locks, deadlocks, race condition (data and flow), etc.

En el siguiente artículo veremos el soporte para Task, las unidades básicas de ejecución dentro de TPL.

Aquí tenéis el código fuente del ejemplo.

http://www.luisguerrero.net/downloads/parallefor.zip

 

Saludos. Luis.