Usar la estructura de datos basada en clave/valor ConcurrentDictionary.
2. Solución
ConcurrentDictionary es una estructura de datos -tipo diccionario- con capacidad de control de acceso -thread-safe-. Para las operaciones de lectura no es necesario bloquear la estructura, sin embargo para escribir nuevos elementos sí (Agafonov, 2013).
Vale agregar que las operaciones Count, IsEmpty, Keys, Values, CopyTo y ToArray requieren bloquear el acceso total a la estructura para poder ser efectuadas. De aquí se recomienda no ser usadas frecuentamente, pues el desempeño se verá significativamente afectado (Aganofov).
Figura 1. Tiempo ejecución lectura y escritura de Dictionary y ConcurrentDictionary.
En la Figura 1 se muestra que el desempeño de escritura de un objeto Dictionary es mucho mejor que un objeto ConcurrentDictionary. Los resultados son mejor en lectura por parte del ConcurrentDictionary, pero no llega a estar muy por encima de Dictionary.
Aunque la prueba anterior no es definitiva respecto al desempeño, sí que nos puede dar una perspectiva esencial para determinar cuál estructura de datos escoger para el tipo de problema que estamos resolviendo.
4. Literatura & Enlaces
Agafonov, E. Multithreading in C# 5.0 Cookbook. Copyright 2013 Eugene Agafonov, 978-1-84969-764-4.
Usar el operadir await con un tipo dinámico de C#.
2. Solución
Para esta receta se usa el paquete ImpromptuInterface: manejo de tipos dinámicos para la versión 4.0 o superiores de C#. Este paquete se agrega desde el administrador de paquetes NuGet (Agafonov, 2013).
Prueba de ejecución: La tarea se está ejecutando en el thread con ID: 1. ¿Thread en el pool de threads?: true La tarea se está ejecutando en el thread con ID: 2. ¿Thread en el pool de threads?: true Repositorio: https://github.com/Fhernd/Recetas.Multithreading.CSharp
1. Introducción 2. Palabras Clave 3. Problema 4. Solución 5. Discusión de la Solución 5.1 Especificación de C# 5.0 para tipos awaitable personalizados 5.2 Interfaz InotifyCompletion Conclusiones Literatura & Enlaces
1. Introducción
Con esta receta C# se desea ilustrar cómo crear un tipo awaitable personalizado compatible con el operador asincrónico await. Más que de una aplicación concreta, el propósito es ilustrar en sentido académico cómo el programador puede crear una implementación personalizada de un tipo awaitable. Se describen los requerimientos para la definición de un tipo awaitable personalizado siguiendo la especificación del parágrafo 7.7.7.1 de la versión de C# 5.0. La sección práctica recurre al uso de la interfaz INotifyCompletion para la calendarización de continuaciones cuando el tipo await personalizado haya finalizado.
2. Palabras Clave
Await
Continuación
Interfaz
INotifyCompletion
3. Problema
Crear una implementación personalizada de un awaitable compatible con la función asincrónica await.
4. Solución
En .NET Framework se cuenta con la interfaz INotifyCompletion para el diseño de un tipo awaitable personalizado.
5. Discusión de la Solución
5.1 Especificación de C# 5.0 para tipos awaitable personalizados
En el parágrafo 7.7.7.1 (Agafonov, 2013) se definen los requerimientos para la definición de expresiones awaitable:
Una expresión t es awaitable si se respeta cualquiera de estas reglas:
La expresión t es un tipo de dato dynamic en tiempo de compilación.
La expresión t tiene un método de instancia o método de extensión llamado GetAwaiter() sin parámetros regulares o tipos paramétricos y un tipo de retorno X que debe cumplir estas especificaciones:
X debe implementar la interfaz System.Runtime.CompilerServices.INotifyCompletion(INotifyCompletion Interface", 2016).
X cuenta con una propiedad de sólo lectura pública de tipo bool llamada IsCompleted.
X cuenta un método de instancia público sin parámetros regulares ni paramétricos llamado GetResult().
Esta descripción está basada en la lectura del documento de especificación del lenguaje de programación C# que el lector puede encontrar en este enlace: CSharp 5.0 Language Specification.pdf.
Esta especificación se sigue en la sección práctica de esta receta para el diseño de un tipo awaitable personalizado.
5.2 Interfaz INotifyCompletion
La interfaz INotifyCompletion (namespaceSystem.Runtime.CompilerServices) representa una operación que programa continuaciones una vez que finaliza su ejecución.
6. Práctica: Diseño de Tipo Awaitable Personalizado
Esta receta sigue el parágrafo 7.7.7.1 de la especificación de C# 5.0.
En primer lugar se implementa el tipo awaitable personalizado:
La clase AwaitablePersonalizado (líneas 6-31) representa la implementación del tipo awaitable personalizado. De acuerdo a la especificación de C# 5.0, esta clase escribe el método GetAwaiter() (líneas 27-30); además retorna el tipo que implementa la interfaz INotifyCompletion: AwaiterPersonalizado.
En las líneas 24-33 se llevan a cabo estas operaciones:
Línea 26: Se crea un objeto del tipo awaitable personalizado para ejecución sincrónica.
Línea 27: Se usa el operador await sobre el tipo awaitable personalizado.
Línea 28: Se muestra en la salida estándar el resultado de la ejecución sincrónica.
Línea 30: Creación de objeto de tipo AwaitablePersonalizado para ejecución asincrónica.
Línea 31: Uso operación asincrónica await para invocación asincrónica.
Línea 32: Muestra en la salida estándar el resultado de la operación asincrónica.
Prueba de ejecución:
7. Conclusiones
Se ha demostrado como en C# es posible crear un tipo awaitable personalizado para operar con la función asincrónica await. Este conocimiento debe alentar al programador a incursionar o sentir curiosidad por temas avanzados que pueden tratarse con C#.
La última receta multithreading de esta serie Usando C# 5.0enseña al programador a usar el operador asincrónico await con el tipo dynamic.
1. Introducción 2. Palabras Clave 3. Problema 4. Solución 5. Discusión de la Solución 5.1 Tipo de retorno void 6. Práctica: Gestión de Tipo de Retorno voiden Métodos Asincrónicas 7. Conclusiones 8. Literatura & Enlaces
1. Introducción
Llega la oportunidad de explorar cómo en un método asincrónico con tipo de retorno void se comporta el flujo de ejecución de una aplicación con múltiples tareas en ejecución. El propósito principal es conocer los problemas que compromete usar este tipo de retorno en la gestión de excepciones por parte del programador. En la sección práctica se demuestra los casos irregulares en la generación de excepciones en dos situaciones: bloque try-catch y una expresión lambda.
2. Palabras Clave
Asincronismo
Excepción
Expresión lambda
Operación asincrónica
Pool de threads
Thread
try-catch
3. Problema
Gestionar el tipo de retorno void en operaciones asincrónicas.
4. Solución
Aunque existen soluciones para gestionar el tipo de retorno void, en esta receta se describe cuáles son los problemas que generar este tipo de retorno en operaciones asincrónicas.
5. Discusión de la Solución
5.1 Tipo de retorno void
El tipo de retorno void especifica que un método no retorna ningún valor una vez ejecutada las operaciones de su implementación. Esto no tiene ningún problema en métodos sincrónicos. Sin embargo, en operaciones asincrónicas y más en particular en métodos que pueden generar excepciones sí que pueden producirse comportamientos anómalos e irregulares.
Continuando, esto es así porque en el contexto de sincronización la gestión de excepciones se lleva a cabo en el pool de threads; entonces, si una excepción no es gestionada, el proceso general terminará de forma abrupta (Agafonov, 2016).
Aún así, existen mecanismos para gestionar las excepciones como el evento AppDomain.UnhandledException. Este evento ocurre cuando una excepción no es capturada/gestionada ("AppDomain.UnhandledException Event", 2016).
6. Práctica: Gestión de Tipo de Retorno void en Métodos Asincrónicos
El siguiente ejemplo adaptado de Agafonov (2013) demuestra cuáles son los problemas que pueden emerger en el uso del tipo de retorno voiden una operación asincrónica.
La primera parte empieza con la descripción del método AsyncRetornoTaskConErrores() (líneas 64-69): este método retorna un objeto Task y genera una excepción simulada por medio de una cadena de texto.
Por otra parte, con AsyncRetornoVoidConErrores() (líneas 75-80) se simula también una excepción con una cadena de texto que incluye la palabra Exception.
El propósito con estos dos métodos es demostrar lo que ocurre al invocar una operación asincrónica que produce una excepción.
Como contraste, los métodos AsyncRetornoTask() (líneas 86-91) y AsyncRetornoVoid() (líneas 97-102) no generan ninguna excepción; sin embargo, en el caso de AsyncRetornoVoid() permite hacer la demostración de lo que ocurre con el tipo de retorno void. Mientras que AsyncRetornoTask() facilita la monitorización del estado de ejecución de la tarea asincrónica, gracias al tipo de retorno, por su parte AsyncRetornoVoid() no; para lograrlo se debe esperar un tiempo debido a que no cuenta con mecanismo de monitoreo de la operación asincrónica.
En cuanto al método Ejecutar() (líneas 15-58), estas son las operaciones que se ejecutan:
Líneas 17-18: Definición de un objeto Task que recibe como retorno el objeto Task de AsyncRetornoTask(). Con t.Wait() se espera unos instantes mientras que la tarea asincrónica se completa.
Líneas 20-21: Invocación de AsyncRetornoVoid(). El monitoreo del estado de la operación asincrónica sólo se logra con un tiempo de espera. Este tiempo de espera es superior al tiempo ejecución simulado por el método. ¿La razón? El tipo de retorno de la operación asincrónica: void.
Líneas 23-29: Invocación de AsyncRetornoTaskConErrores(). El ciclo while comprueba que mientras no se genere ninguna falla se simula un tiempo de espera. Al final, línea 27, se muestra la excepción que se produjo con la invocación de esta método.
Líneas 31-55: Sirve al lector como comprobación del comportamiento irregular del manejo de excepciones de operaciones asincrónicas que retornan el tipo void. El lector puede tomar este código y verificar por medios propios lo que ocurre cuando se remueven los comentarios.
Esta es la demostración del código en ejecución:
7. Conclusiones
Quedó demostrado cómo gestionar el tipo de retorno void de métodos asincrónicos. El no reconocer las problemas en manejo de excepciones en métodos con esta clase de firma puede hacer que un programa genere un comportamiento irregular y afecte la experiencia del usuario.
La próxima receta multithreading explicará cómo diseñar un tipo personalizado de await.
1. Introducción 2. Palabras Clave 3. Problema 4. Solución 5. Discusión de la Solución 5.1 Método Task.ConfigureAwait() 6. Práctica: Cambio de Ejecución Asincrónico 7. Conclusiones 8. Literatura & Enlaces
1. Introducción
El modo de operación de la invocación de una función asincrónica await puede ser alterado, por ejemplo, en la ejecución de operaciones asincrónicas de una interfaz gráfica de usuario. Esta receta multithreading en C# explica cómo usar el método ConfigureAwait() para configurar la ejecución asincrónica de código que no accede a los componentes de la interfaz gráfica de usuario. La demostración en la sección práctica enseña el uso de este método alternando entre el uso del contexto de sincronización y sin recurrir a él.
2. Palabras Clave
Asincronismo
Contexto de sincronización
Interfaz gráfica de usuario
GUI
Sincronismo
3. Problema
Configurar el modo de ejecución asincrónico de una tarea.
4. Solución
El método ConfigureAwait() permite la configuración del modo de ejecución asincrónico.
5. Discusión de la Solución
5.1 Método Task.ConfigureAwait()
El método ConfigureAwait() configura el modo de ejecución asincrónica de una tarea. Este método recibe como argumento un valor booleano que en caso de ser true, el modo de ejecución asincrónica captura el contexto desde donde es ejecutada la tarea; en caso contrario la tarea se debe ejecutar sin capturar el contexto de sincronización.
Esta es su firma:
public ConfiguredTaskAwaitable ConfigureAwait(
boolcontinueOnCapturedContext
)
6. Práctica: Cambio de Ejecución Asincrónica
La siguiente receta C# explica cómo el programador puede cambiar el modo de ejecución asincrónica.
El método que controla el evento Click (líneas 48-69) del botón que activa las operaciones asincrónicas lleva a cabo estas operaciones:
Línea 51: Asigna el texto Calculando... cuando se inicia la ejecución asincrónica de dos tareas que operan en asincronismo con y sin contexto.
Línea 54: Inicia la ejecución de una operación con contexto.
Línea 55: Inicia la ejecución de una operación sin contexto.
Líneas 61-65: Construcción de la cadena de resultados de las dos operaciones asincrónicas anteriores.
Línea 68: Asigna el nuevo texto a la etiqueta de la interfaz gráfica de usuario.
Por otra parte, con el método OperacionContexto() (líneas 75-90) realiza estas operaciones:
Línea 77: Especifica el número de iteraciones que ejecuta este método asincrónico.
Líneas 78-79: Creación e inicio de cronómetro.
Líneas 81-85: Repite la ejecución de una tarea asincrónica simulada. Aquí la tarea se ejecuta capturando implícitamente el contexto de ejecución.
Línea 87: Se detiene el cronómetro.
Línea 89: Retorna el tiempo que tomó la ejecución.
De manera análoga, el método OperacionSinContexto() (líneas 96-112) ejecuta estas operaciones:
Línea 98: Especifica el número de iteraciones que ejecuta este método asincrónico.
Líneas 99-100: Creación e inicio de cronómetro.
Líneas 102-107: Repite la ejecución de una tarea asincrónica simulada. A través del método ConfigureAwait() se establece que el método se debe ejecutar sin capturar el contexto de sincronismo.
Línea 109: Se detiene el cronómetro.
Línea 111: Retorna el tiempo que tomó la ejecución.
Finalmente, con el método Ejecutar() (líneas 19-41) se construye la interfaz gráfica de una aplicación Windows Presentation Foundation (WPF).
Prueba de ejecución:
7. Conclusiones
Se ha demostrado cómo a través del método ConfigureAwait() se puede alterar el modo de ejecución de una operación asincrónica: con y sin captura de contexto de sincronismo. Quedó claro que cuando no se captura el contexto el desempeño general de la aplicación es superior comparado con la captura del contexto.
Hasta ahora se ha estudiado los métodos asincrónicos que retornan un tipo paramétrico para el tipo Task, sin embargo en la siguiente receta multithreading se estudiará cómo trabajar con operaciones asincrónicas que retornan void.
1. Introducción 2. Palabras Clave 3. Problema 4. Solución 5. Discusión de la Solución 5.1 Combinación de tareas con Task.WhenAll 6. Práctica: Ejecución en Paralelo de Tareas 7. Conclusiones 8. Literatura & Enlaces
1. Introducción
Aquí se explica cómo el programador puede lograr la ejecución en paralelo de múltiples tareas usando la función asincrónica await. El ejemplo de la sección práctica demuestra que esto es posible a través de la ejecución de dos tareas, cada una con 3 y 5 segundos de retrasa. Al final quedará demostrado que el tiempo máximo de espera será 5 segundos.
2. Palabras Clave
await
Ejecución en paralelo
Función asincrónica
Tarea
3. Problema
Ejecutar en paralelo dos o más tareas.
4. Solución
A través del operador await el programador puede implementar código de ejecución en paralelo.
5. Discusión de la Solución
5.1 Combinación de tareas con Task.WhenAll
El método staticTask.WhenAll()("Task.WhenAll Method", 2016) crea una tarea compuesta; una vez que el conjunto de tareas integrales finalicen su ejecución, el flujo de control de ejecución pasa a la tarea compuesta y se continua con la lógica de implementación particular a esta tarea.
Este método cuenta hasta con 4 versiones static sobrecargadas; tal y como se muestra en la siguiente figura:
Figura 1. Versiones sobrecargadas de Task.WhenAll("Task.WhenAll Method", 2016).
6. Práctica: Ejecución en Paralelo de Tareas
El siguiente ejemplo en C# demuestra la ejecución de dos tareas en paralelo a través del uso de la función asincrónica await.
El método ObtenerInfoAsync(string, int) (líneas 46-54) representa una operación de ejecución asincrónica. Estas son las operaciones que lleva a cabo:
Línea 48: Simula la ejecución extendida.
Líneas 50-53: Retorna cadena de caracteres con la información del thread que ejecuta la tarea.
Por otra parte, con el método ProcesamientoAsincronico() (líneas 25-38) se preparan para ejecución dos tareas; cada una con duración de 3 y 5 segundos. Los resultados se almacenarán en el arreglo de objetos stringresultados (línea 30). Cuando estas dos tareas hayan finalizado su ejecución, se muestran los resultados de los threads de ejecución en el ciclo foreach de las líneas 34-37.
En esta captura se presenta el flujo de ejecución de esta interesante receta:
El operador await permite al programador del lenguaje de programación C# ejecutar en paralelo dos o más tareas. Hasta ahora se ha visto que el modelo TPL resulta más intuitivo y poderoso que los modelos EAP y APM estudiados en series de recetas multithreading anteriores.
La próxima receta multithreading enseña al programador cómo manipular excepciones en operaciones asincrónicas.
1. Introducción 2. Palabras Clave 3. Problema 4. Solución 5. Discusión de la solución 5.1 Operador asincrónico await 5.2 Operador asincrónico async 6. Práctica: Expresión Lambda con Operador Asincrónico await 7. Conclusiones 8. Literatura & Enlaces
1. Introducción
El programador C# puede usar el operador asincrónico await en una expresión lambda. Esta receta multithreading explica cómo usar este operador asincrónico en el cuerpo de declaración, junto con el uso de async en listado de parámetros de la expresión lambda. Para llevar a cabo la demostración programática de esta receta se usará el delegado estándar Func<T, TResult>.
2. Palabras Clave
Asincronismo
Expresión lambda
Sincronismo
3. Problema
Definir la implementación de una expresión lambda usando el operador await.
4. Solución
El cuerpo de la implementación de una expresión lambda puede incluir la invocación de operaciones por medio de await; sin embargo para esto es necesario marcar la firma de la expresión con el operador asincrónico async. Esta expresión lambda quedará declarada sobre un delegado del tipo Func<T, TResult>.
A través del operador asincrónico el programador C# puede marcar un método, expresión lambda o método anónimo como asincrónico ("async (C# Reference)", 2016). Este un ejemplo para marcar un método nombrado:
6. Práctica: Composición de una Expresión Lambda con el Operador Asincrónico await
La aplicación consola que se ha construido demuestra cómo definir una expresión lambda especificando en su cuerpo de implementación el operador asincrónico await.
Se ha demostrado que dentro del cuerpo de una expresión lambda es posible usar la función asincrónica await para invocar otras operaciones asincrónicas. Lo anterior siempre y cuando el programador especifique la función asincrónica async en la firma de parámetros de la expresión.
La siguiente receta multithreading explica cómo usar el operador asincrónico await para llamadas asincrónicas consecutivas de tareas.