lunes, 25 de septiembre de 2017

Uso de SecureShell con Raspberry y Windows IoT

PowerShell Windows IoT

Windows IoT Remote Client

Windows IoT Remote Client

Kit de Inicio Windows 10 IoT Core para Raspberry PI2

Depuracion y Despliegue de Aplicacion UWP sobre Raspberry Pi

Desarrollo de una Aplicacion UWP

Plataforma Universal de Windows

Accediendo al Panel de Administracion

Panel de Administracion Win IOT Core

Instalacion Windos IoT en Raspberry Pi

Instalando DisplayTouch y Raspberry Pi 2 en Case SmartiPi

Kit de Inicio Windows 10 IoT Core para Raspberry PI2

miércoles, 19 de septiembre de 2012

EL SDK Visualizador de Concurrencia: Técnicas Avanzadas de Visualización.

 

En el post anterior , pudimos verificar en diferentes escenarios el uso del visualizador de concurrencia, el dia de hoy quiero mostrarles unas técnicas mejoradas con el fin de obtener un mayo control sobre la dorma de visualizar los eventos mas significativos dentro de una aplicación.

Con el fin de ilustrar bien los comportamientos de la aplicación, voy a utilizar a manera de simulación aplicación simula el proceso que lleva a una persona del común ir al trabajo, Se simularan cambios de 4 procesos usando múltiples iteraciones (Cada Iteracion representa una hora en tiempo de simulación).

Para cada iteración la aplicación realiza los siguientes cuatro cálculos:

1. Levantarse.

2. Bañarse.

3. Desayunarse.

4. Salir.

En esta simulación se puede realizar en paralelo y usare C# y el Thread Poll para simular la carga de trabajo de paralelismo.

Sin explicar mucho el código a continuación presento el código con el cual trabajaremos:

static void Main(string[] args)

{

MarkerWriter miwriter =

new MarkerWriter(new Guid("9f34bddd-eae4-40c9-b668-91d78fc0b326"));

MarkerSeries series = miwriter.CreateMarkerSeries("");

GradoParalelismo = 2;

numIterations = 24;

DateTime simTime = new DateTime(2012, 09, 18, 0, 0, 0);

eventoManuales = new ManualResetEvent[GradoParalelismo];

for (int i = 0; i < eventoManuales.Length; i++)

{

eventoManuales[i] = new ManualResetEvent(false);

}

//Cada Iteracion representa una hora de las actividades del dia

for (int n = 0; n < numIterations; n++)

{

series.WriteFlag("Iteration " + n + ": time = " + simTime.TimeOfDay);

//Etapa 1 - Levantarse

for (int i = 0; i < GradoParalelismo; i++)

{

eventoManuales[i].Reset();

var span = series.EnterSpan("Levantadose");

TaskInfo ti = new TaskInfo("Levantadose", i);

ThreadPool.QueueUserWorkItem(Levantarse, ti);

Thread.Sleep(100);

span.Leave();

}

WaitHandle.WaitAll(eventoManuales);

//Etapa 2 -Bañarse

for (int i = 0; i < GradoParalelismo; i++)

{

eventoManuales[i].Reset();

var span = series.EnterSpan("bañandose");

TaskInfo ti = new TaskInfo("bañandose", i);

ThreadPool.QueueUserWorkItem(Banarse, ti);

Thread.Sleep(100);

span.Leave();

}

WaitHandle.WaitAll(eventoManuales);

//Etapa 3 - Desayunar

for (int i = 0; i < GradoParalelismo; i++)

{

eventoManuales[i].Reset();

var span = series.EnterSpan("Desayunandose");

TaskInfo ti = new TaskInfo("Desayunandose", i);

ThreadPool.QueueUserWorkItem(Desayunar, ti);

Thread.Sleep(100);

span.Leave();

}

WaitHandle.WaitAll(eventoManuales);

//Etapa 4 - Salir

for (int i = 0; i < GradoParalelismo; i++)

{

eventoManuales[i].Reset();

var span = series.EnterSpan("Saliendo a trabajar");

TaskInfo ti = new TaskInfo("Saliendo a trabajar", i);

ThreadPool.QueueUserWorkItem(Salir, ti);

Thread.Sleep(100);

span.Leave();

}

WaitHandle.WaitAll(eventoManuales);

simTime = simTime.AddHours(1);

}

Console.ReadKey();

}

Como podemos ver tenemos una variable estática llamada GradoParalelismo, el cual define la cantidad de hilos que van a participar de manera simultanea en la simulación.

Dado que los resultados de cada fase dependen de la fase anterior, usaremos una variable llamada (eventoManuales) con el fin de tener un punto de reset , de tal forma que los hilos no continuaran la siguiente fase hasta que todos hayan finalizado la fase actual.

En este ejemplo , Se ejecutara en una maquina con un Core i7 Processor. Con la variable GradoParalelismo = 2 y ejecuto el código en dos iteraciónes.

Con el fin de tener un idea generalizada de cómo se comporta la aplicación correremos el visualizador de concurrencia, para saber cuál de la etapas se encuentra más equilibrada y desde luego la duración de cada una de ellas.

La imagen que se presenta a continuación muestra el comportamiento de la aplicación sobre el visualizador de concurrencia.

Según la grafica anterior, lo que podemos visualizar, es que el hilo anterior encola los elementos de trabajo y los bloquea mientras avanza el programa. Como se puede visualizar existen varios hilos ejecutando el trabajo (Color Verde). Los hilos no están 100% ocupados, sin embargo , a partir de esta imagen es difícil saber a que horas se lleva cada fase.

En el post anterior , realizamos una introducción de como se usa un SPAN para demarcar la ejecución de una fase. Pero a diferencia del ejemplo anterior lo que desea realizar es tener un Marcador ( Marker) personalizado, en lugar de utilizar el predeterminado.

lunes, 10 de septiembre de 2012

Caller Information con C# en Visual Studio 2012:

 
Mediante el uso de atributos del caller info, usted ahora puede tener información acerca de quien esta llamando un método. Puede obtener la ruta del archivo del código fuente, el numero de la línea, y el nombre del miembro del llamador. Esto puede ser útil para realizar el seguimiento, depuración y sirve como herramienta de diagnostico.
Los atributos de información de llamantes, aparecen como caídos del cielos para personas dedicadas a realizar métodos para diagnostico de una aplicación, ya que normalmente para esto lo que realizaba el desarrollador era la implementación de archivo de registro (log) que guardara lo que sucedía en determinado momento cuando era llamado un método, sin embargo para saber cual método estaba llamado al parámetro de la solución muchas veces era necesario trabajar con reflection para saber el nombre del método, o lo que realizada era implementar la interfaz NotifyPropertyChanged. Ahora sencillamente el desarrollador proporciona el atributo adecuado para los parámetros de una función con los valores que vienen por defecto y magia!!!! AHORA PODEMOS SABER CUAL ES EL METODO QUE ESTA LLAMANDO , sin tanto código.
Para obtener esta información, se utilizan atributos que tienen parámetros opcionales, los cuales tienen un valos por defecto. El espacio de nombre es System.Runtime.CompilerServices.
A continuación le nombro estos atributos:
CallerFilePathAttribute: Ruta completa del archivo que contienen el llamador. Esta ruta esta dada en tiempo de compilación y es de tipo string.
CallerLineNumbreAttibute: Numero de la línea de archivo origen en el cual el método es llamado y es de tipo string.
CallerNameAttribute: Entrega nombre del método o de la propiedad del llamante es de tipo string.
Veamos el siguiente ejemplo en donde tenemos un métodos principal llamado main del cual realizamos un llamado a un método para que imprima la ubicación del archivo del proyecto desde donde se esta ejecutando, la línea de código desde donde se esta llamando, y el nombre de método que lo llama.
El código fuente es el siguiente:
image
Al ejecutar este código lo que presentara en la ventana de consola es lo siguiente:
clip_image002

Ahora veamos que sucede si creamos otro método llamado OtroMetodo y desde el nuevo método llamamos a al método que tiene el decorado con los atributos de Caller Information.
clip_image004

Como podemos visualizar, en la función que llama ahora dice Otro Método y en la línea desde donde se llama es la línea 18 de nuestro código fuente.

DESCARGUE EL CODIGO DE EJEMPLO AQUI












domingo, 2 de septiembre de 2012

USO de Mensajes (MESSAGE) con el SDK del Visualizador de Concurrencia


Cuando se requiere tener un archivo de registro de datos, con el fin de que nos pueda servir para diagnóstico de desempeño de la aplicación o para fines de depuración de código, generalmente lo que hacemos es crear un archivo que guarde la información que se requiere a monitorear, sin .embargo lo que sucede es que este archivo no se revisa constantemente, y en caso de hacerlo, lo más probable es la mayoría de la información guardada en el archivo de registro se omite.
clip_image002
Cuando se requiere este tipo de información se recomienda utilizar la palabra clave Message del SDK del visualizador de concurrencia . Supongamos que vamos a medir la cantidad de tiempo para generar un archivo de log que mida el tiempo requerido para su generación, para una operación de escritura de un archivo.
El código para probar esta funcionalidad lo especifico a continuación y vale la pena resaltar que en el ovalo de color azul se encuentra la palabra clave Message, la cual especifica lo que se va a adicionar en los Markers.
Lo que realiza este código es listar los directorio que están en mi disco duro C, y se almacenara esta información en un archivo de texto en el mismo disco, lo importante es que por cada operación de escritura se monitoreara el tiempo en que realiza dicha escritura.
Al ejecutar el analizador de concurrencia, lo que veremos son unos pequeños rectángulos que representan el mensaje del tiempo de cada escritura, (Ver ovalo de color Naranja en la siguiente grafica), Si usted necesita ver de manera mas especifica la información de salida del mensaje , lo que puede realizar es seleccionar desde el informe el ítem Markers ubicado en la parte inferior izquierda llamado Visible TimeLine Profile y al dar click en ello, podremos visualizar de manera granular la información del mensaje-
clip_image004

Este reporte enumera todos los Markers en el intervalo de tiempo visible actualmente, para este caso son 18 Markers, ya que en mi disco duro únicamente tengo esta cantidad de carpetas, sobre este mismo cuadro podrás visualizar, en tiempo en que tarda en escribir el nombre del directorio al archivo ubicado en el disco D.
Si queremos exportar la información de estos Markers, en este listado existe un botón llamado export el cual me permitirá exportar esta información a archivo de Texto o un archivo csv.
clip_image006
clip_image008
Estos pequeños artículos muestran a ustedes una nueva funcionalidad del para Visualización de concurrencia, sin embargo el ejemplo es lo mas básico y sencillo, con el fin de que las personas que están comenzando a manejar la depuración de concurrencia se les haga mas fácil entender como función. Existen maneras mas complejas de este SDK, como por ejemplo colocando categorías, niveles de importancia y manejo de múltiples Markers. Este tipo complejo de información lo dejare para posteriores post.
 
DESCARGUE EL CODIGO DE EJEMPLO AQUI











USO de Bandera (FLAG) con el SDK del Visualizador de Concurrencia

Cuando se requiere marcar un evento especifico en nuestras aplicaciones en donde no es valioso saber el tiempo en que dura el evento, sino sencillamente en que momento ocurre, lo más adecuado es usar FLAG, lo que hace en realidad es marcar en el canal con un icono en donde ocurrió el evento y desde luego el nombre del mismo. Un ejemplo de esto podría ser saber en qué momento de nuestra aplicación ocurrió un excepción.
Continuando con el ejemplo anterior lo que podremos es ver en qué momento se inicia cada uno de los métodos pero no cuánto dura, la sintaxis del uso del flag es como aparece a continuación:
clip_image002
Al ejecutar el código y analizarlo por medio del visualizador de concurrencia, obtendremos lo siguiente:

clip_image004

Espero el articulo les sirva y cualquier duda estare atento a ella. Adjunto envio codigo fuente del ejemplo.


DESCARGUE EL CODIGO DE EJEMPLO AQUI




Uso de Duración (SPAN): SDK Visualizador de Concurrencia

Como definimos anteriormente, un SPAN representa una fase de la aplicación, en el ejemplo mostrado al inicio de este blog, no es fácil conocer el trabajo realizado por cada uno de los métodos tanto Metodo1 como el Metodo2.
clip_image001



Si revisamos la grafica anterior podermos ver que es lo que esta pasando en el hilo principal, pero no tenemos idea que sucede específicamente,en que momento se esta ejecutanto cada uno de los métodos. Lo que podríamos hacer para deducir que es lo que esta realizando , es revisar cada uno de los segmentos de color verde y mirar el llamado de la pila, para determinar en donde finaliza cada uno de los métodos y donde inicia el siguiente, pero esto no necesariamente me garantiza lo que en verdad esta sucediendo. El nuevo SDK nos ayuda a poder intrumentar nuestras aplicaciones en paralelo, miremos como se puede hacer en el siguiente código.
clip_image003

Al compilar y analizar con el visualizador de concurrencia, obtenemos la siguiente grafica en donde aparece en la columna Izquierda un nueva fila la cual es llamada CANAL, y es nombrada con el espacio de Nombres del SDK, (ConcurrencyVisualizer.Markers), en esta fila ya podemos ver en qué momento se está iniciando y finalizando, cada uno de los métodos con respeto al hilo principal de ejecución. Fijemos que la separación entre cada uno de los métodos conrresponde a la impresión que se realiza en la consola diciendo que el método 1 a terminado.
clip_image005



Como vemos el tiempo de cada uno de los métodos, visualizamos que el metodo1 tarda menos que el Metodo 2 ya que el ciclo del metodo1 va hasta 100 y el del Metodo2 va hasta 200. De hecho si seleccionamos con el Mouse cada uno de los Metodos, podremos saber cual es el tiempo de ejecución de cada uno de ellos, Por ejemplo:
clip_image009 clip_image008

El metodo1 donde el ciclo va hasta 100 tarda 18.5279 Milisegundos y el Metodo2 donde el ciclo va hasta 200 tarda 22.0959 ms.
Fijemos que el ID del hilo asociado con el canal Marker es el mismo ID que del hilo principal, visualizando en la grafica es el Hilo con ID 6544, este ID esta ubicado al lado izquiero del nombre del canal. Esto indica que los eventos del Marker fueron generados desde el hilo principal. Si estos eventos son generado a partir de un hilo diferente, el canal estará asociado con el ID desde el hilo que lanzo el evento.
Espero puedan practicar con el fin de que se familiarizen con el uso de este SDK.

DESCARGUE EL CODIGO DE EJEMPLO AQUI










El nuevo SDK para el visualizador de concurrencia en VS2012

 

El visualizador de concurrencia muestra información importante tanto de la CPU, como de la GPU y recursos importantes como la actividad de DirectX, dispositivos de I/O y de los procesos que se ejecutan entre otros. el visualizador muestra el promedio de utilización de núcleo del proceso. Los procesos libres, los procesos del sistema y otros procesos que son ejecutados en tiempo del sistema.

Por ejemplo si dos núcleos están ocupando en un 50% de su capacidad en un periodo de tiempo determinado, entonces esta vista muestra el procesador lógico que está siendo utilizado.

Esta información puede ser muy valiosa en la investigación de comportamiento de una aplicación, pero a veces es difícil de entender rápidamente cómo los datos que se muestran en los mapas visualizador de concurrencia en el comportamiento de la aplicación. Visual Studio 2012 trae consigo un SDK que permite instrumentar el código con el fin de aumentar las visualizaciones mostradas en la vista Hilos de las visualizador de concurrencia. Estas visualizaciones, denominados marcadores “Marker”, hace que la vista de hilos sea semánticamente entendible porque representan las fases y eventos específicos de la aplicación.

Y después de tanta teoría comencemos con lo interesante, usarlo !!! , miremos donde ubicarlo en el IDE de Visual Studio.

 

clip_image002

En el menú de herramientas en análisis, visualizador de concurrencia

El SDK contiene tres opciones visuales , las cuales son:

1.Span: Representa un intervalo de tiempo en la aplicación. Como lo es una fase de la aplicación.

2. Flag: Representa un único punto en el tiempo ( por ejemplo cuando se llega a un valor predeterminado o cuando ocurre una excepción).

3.Message: Representa un único punto en el tiempo, pero se entiendo como un análogo al clásico evento de estilo trazado.

Para cada una de estas tres opciones generaremos un ejemplo sobre ellas, aunque lo hare con ejemplos básicos para poderlo trabajar.

Vamos a crear un pequeño proyecto con dos métodos, los cuales cada una realizara un ciclo de 1 a 10000, y el otro de 100000 a 20000 con esto comenzaremos a que la aplicación se presentaría de la siguiente manera:

class Program

{

static void Main(string[] args)

{

Metodo1();

Console.WriteLine("Terminado Metodo 1");

Metodo2();

Console.WriteLine("Terminado Metodo 2");

}

private static void Metodo2()

{

for (int i = 0; i < 100; i++)

{

Console.WriteLine(i);

}

}

private static void Metodo1()

{

for (int i = 10000; i < 200; i++)

{

Console.WriteLine(i);

}

}

}

Fijémonos que el código se ejecuta de forma secuencial y se presenta de la siguiente manera:

clip_image004

Ahora vamos a iniciar la depuración con el visualizador de concurrencia de tal formas que realizamos lo siguiente:

clip_image006

Al dar click en iniciar con el actual proyecto, el ejecutable se iniciar realiza la ejecución de cada uno de los métodos de forma secuencial. Y al terminar nos presentara una gráfica como la siguiente:

clip_image008

clip_image010

Utilizando el SDK en el proyecto:

Para comenzar es necesario adición el SDK al proyecto, esto se realizar de la siguiente manera, vamos a al menú de herramientas, opción analizar, luego Visualizador de concurrencia, adicionar el SDK al proyecto.

clip_image012

Aparecera una ventana en donde le preguntara que proyectos de la solución desea adicionar al visualizador de concurrencia, en el caso que el proyectos tenga varios proyectos estos aparecerán en la lista a seleccionar ( ver Grafica).


clip_image016clip_image014

Luego de seleccionar el proyecto , se presiona el botón adicionar sdk al proyecto y aparecerá una nuevamente confirmando que el SDK fue adicionado al proyecto seleccionado, y lo podemos validar al verificar las referencias del proyectos en donde aparecerá una nueva referencia llamada Microsoft.ConcurrencyVisualizer.Markers y luego se da click en el botón Cerrar.

clip_image020 clip_image018

Despues de esto se debe adicionar el espacio de nombres en el código fuente como aparece a continuación:

using System;

using System.Threading.Tasks;

using Microsoft.ConcurrencyVisualizer.Instrumentation;

namespace ConsoleApplicationConcurrent

{

Comencemos ahora las opciones de visualización que trae el SDK de visualización de concurrencia.

clip_image022

En el siguiente articulo comenzaremos con la utilización de alguno de estas nuevas funcionalidades tales como Span, Marker, Message.

jueves, 23 de agosto de 2012

Paralelismo con Tareas ( Task Parallelism)

 

Comencemos enunciando que las tareas son los elementos fundamentales de las programación en paralelo, por lo tanto el paralelismo de tareas es la ejecución de tareas a través de varios procesadores.

El propósito principal del paralelismo es maximizar la utilización del procesador y desde luego mejorar el desempeño de la aplicación. A medida que aumenta la cantidad de procesadores,una aplicación podrá escalarse ya que una tarea podrá ejecutarse en mas procesadores.

Las tareas paralelas tienen unas entradas , que típicamente trabajan con una colección de datos relacionados.

Que son Tareas ( Task)?

En la actualidad existen varios caminos para invocar tareas en paralelo. En los articulo que escribiré sobre ello, hablare sobre esto, iniciando con el método Parallel.Invoke.

Asumamos que tenemos tres métodos ( A,B,C) , y que cada uno de estos métodos son independientes entre si y no tienen relación entre ellos. Si los métodos se ejecutan de maneras secuencial el tiempo de respuesta de ellos será la sumatoria de duración de cada uno de ellos.

Si por ejemplo el MétodoA tarda 10Ms, MetodoB tarda 20Ms, y MetodoC tarda 10MS, el tiempo total de duración cuando se ejecuta de manera secuencial es de 40 Milisegundos.

 

clip_image002

Cuando estos métodos son convertidos en tareas (Task), las tareas son completamente independiente, esto hace que cuando se ejecutan tareas en paralelo ya no es necesario ningún tipo de sincronización tales como lo que hemos vistos en mis artículos anteriores como semaphore o Monitor. Por lo tanto el desempeño en la utilización de tareas es mucho mejor y la porción del código que se ejecuta en una tarea (Task) se vuelve escalable. Además la sincronización como todos sabemos hace mas complejo el mantenimiento y la depuración.

Cuando un grupo de tareas se ejecuta en paralelo, el tiempo de ejecución es igual al tiempo de ejecución de la tarea mas larga. En la siguiente imagen vemos un ejemplo de lo que puede tardar los métodos cuando se ejecutan como tareas desde luego en paralelo, para concluir el tiempo de ejecución es la mitad que cuando se hace de forma secuencial, con este ejemplo asumimos desde luego que hay suficientes procesadores libres por lo menos tres.

 

Captura de pantalla 2012-08-23 a la(s) 16.10.54

 

Las tareas son programadas y asignadas a un hilo (thread), los hilos son ejecutados por un procesador. Un hilo es un camino de ejecución asíncrona sobre un proceso. Como hemos hablado un hilo por si solo no hace nada, la actividad es realizada por los hilos. De hecho el sistema operativo cierra automáticamente un proceso que no tiene hilos. debido a que no existe motivo alguno para que mantenga un proceso que no tiene actividad alguna.

A pesar de esto, un proceso es, sin embargo importante. Provee los recursos necesario para la ejecución de un programa, tales como memoria virtual , la pila y demás recursos necesarios.

Hilos (Threads)

Las tareas se ejecutan sobre los hilos. Los hilos son el motor de su aplicación y representan el código que se esta ejecutando. Entendiendo los hilos fácilmente podremos entender que hace una tarea.

Estos es importante porque parte de la sobrecarga vienen asociadas con las tareas relacionadas con los hilos. Además los hilos tienen gran relevancia cuando se habla de programación en paralelo.

El sistema operativo programa los hilos de forma preventiva. Los hilos son asignados y ajustados con una prioridad especifica y en general son programados por partes iguales basándose de la prioridad de los hilos. A continuación  enumero el porque un hilo puede ser anulado:

 

  • Porque el Hilo excede el tiempo de ejecución.
  • Porque la prioridad de inicialización es alta.
  • Porque se ponen en espera.
  • Porque ocurre una operación de entrada y salida.

Nada en pro de desempeño de la aplicación es gratis, incluyendo los hilos. Los hilos tienen sobrecargas, y esta sobrecarga vienen asociada con la sobrecarga en la pila o stack, almacenamiento local de hilos, y multiplexacion de los contextos de hilos. Por defecto el tamaño pila para cada hilo es de 1 MB. Por ejemplo 200 hilos podrían reservar 200MB de espacio en memoria. El almacenamiento local de hilos es un conjunto de memoria privada reservada por cada hilo y puede también ser significante. Adicionalmente a la sobrecarga de almacenamiento, los hilos tienen un costo de desempeño : por los cambios de contexto.

Gran parte de este costo esta asociado con el intercambio entre el modo Kernel y modo usuario y el canje de un hilo por otro modo de hilo. El costo de cambio de contexto puede reducir el beneficio de hilos adicionales.

Ademas de costo de cambio de contexto, existen otros costos tales como aumento gradual de hilos y su destrucción. El framework de .Net trae una ayuda  para llamada “Thread Pool” , para administrar estos costos y abstraer de complejidad de la creación, iniciación y destrucción de hilos.

El “Thread Pool”  grupo de hilos, comúnmente reúsa los hilos para disminuir los costos de iniciación y destrucción, cuando el hilo ya no se requiere, ele sistema operativo reasigna un trabajo adicional al hilo o lo suspende.

Existen algoritmos para ajustar el Tamaño “Thread Pool”, dinámicamente basados sobre la utilización de los hilos y otros factores.

El Framework 4 de .Net “Thread Pool” es el programador por defecto para las tareas en paralelo. Cuando se inicia una tarea, esta es programada para ejecutarse y ponerse en cola como parte del pool de hilos. Después las tareas son desencoladas y asignadas a ejecutarse sobre el hilo disponible. Afortunadamente esta tarea es transparente para el desarrollador.

En resumen, una tarea es un grupo de estados relacionados, cuando son iniciados son adicionados a la cola pool de hilos. Eventualmente (puede ser instantánea) , una tarea es ejecutada sobre un hilo, el cual es la unidad de ejecución. Desde luego un hilo que pertenezca al pool de hilos.

Cada hilo es asignado y inicialmente ejecutado sobre un procesador en particular, el cual es considerado como la unidad de proceso. El siguiente diagrama muestra la relación entre una tarea, un hilo y un procesador.

clip_image002[5]

Espero este pequeño articulo les ayude a ustedes, a entender un poco sobre como es el comportamiento de una tarea  y en mis proximos articulos hablare sobre los metodos parallel invoke, TaskFactory.StartNew, Task.Start. Saludos….