Resumen del capítulo 20. Asincronía y E/S de archivos
Nota:
Este libro se publicó en la primavera de 2016 y no se ha actualizado desde entonces. Gran parte del libro sigue siendo útil, pero algunos de los materiales están anticuados y algunos temas ya no son completamente correctos o completos.
Una interfaz gráfica de usuario debe responder de forma secuencial a los eventos de entrada del usuario. Esto implica que todo el procesamiento de eventos de entrada del usuario debe realizarse en un único subproceso, a menudo denominado subproceso principal o subproceso de interfaz de usuario.
Los usuarios esperan que las interfaces gráficas de usuario respondan. Esto significa que un programa debe procesar rápidamente los eventos de entrada del usuario. Si eso no es posible, el procesamiento se debe relegar a subprocesos secundarios de ejecución.
Varios programas de ejemplo de este libro han utilizado la clase de WebRequest
. En esta clase, el método BeginGetResponse
inicia un subproceso de trabajo, que llama a una función de devolución de llamada cuando está completa. Sin embargo, esa función de devolución de llamada se ejecuta en el subproceso de trabajo, por lo que el programa debe llamar al método Device.BeginInvokeOnMainThread
para tener acceso a la interfaz de usuario.
Nota:
Los programas de Xamarin.Forms deben usar HttpClient
en lugar de WebRequest
para acceder a los archivos a través de Internet. HttpClient
admite operaciones asincrónicas.
Un enfoque más moderno para el procesamiento asincrónico está disponible en .NET y C#. Esto implica las clases Task
y Task<TResult>
y otros tipos en los espacios de nombres System.Threading
y System.Threading.Tasks
, así como las palabras clave async
y await
de C# 5.0. Esto es en lo que se centra este capítulo.
De las devoluciones de llamada a await
La propia clase Page
contiene tres métodos asincrónicos para mostrar cuadros de alerta:
DisplayAlert
devuelve un objetoTask
.DisplayAlert
devuelve un objetoTask<bool>
.DisplayActionSheet
devuelve un objetoTask<string>
.
Los objetos Task
indican que estos métodos implementan el modelo asincrónico basado en tareas, conocido como TAP. Estos objetos Task
se devuelven rápidamente desde el método. Los valores devueltos de Task<T>
constituyen una "promesa" de que un valor de tipo TResult
estará disponible cuando se complete la tarea. El valor devuelto Task
indica una acción asincrónica que se completará pero sin ningún valor devuelto.
En todos estos casos, Task
se completa cuando el usuario descarta el cuadro de alerta.
Una alerta con devoluciones de llamada
En el ejemplo AlertCallbacks se muestra cómo controlar los objetos devueltos Task<bool>
y las llamadas Device.BeginInvokeOnMainThread
mediante métodos de devolución de llamada.
Una alerta con lambdas
En el ejemplo AlertLambdas se muestra cómo usar las funciones lambda anónimas para administrar llamadas Task
y Device.BeginInvokeOnMainThread
.
Una alerta con await
Un enfoque más sencillo implica las palabras clave async
y await
presentadas en C# 5. En el ejemplo AlertAwait se muestra su uso.
Una alerta con nothing
Si el método asincrónico devuelve Task
en lugar de Task<TResult>
, el programa no necesita usar ninguna de estas técnicas si no necesita saber cuándo se completa la tarea asincrónica. En el ejemplo NothingAlert se muestra esto.
Guardar la configuración del programa de forma asincrónica
En el ejemplo SaveProgramChanges se muestra el uso del método SavePropertiesAsync
de Application
para guardar la configuración del programa a medida que cambian sin reemplazar el método OnSleep
.
Un temporizador independiente de la plataforma
Se puede usar Task.Delay
para crear un temporizador independiente de la plataforma. En el ejemplo TaskDelayClock se muestra esto.
Entrada y salida de archivos
Tradicionalmente, el espacio de nombres System.IO
de .NET ha sido el origen de la compatibilidad de E/S de archivos. Aunque algunos métodos de este espacio de nombres admiten operaciones asincrónicas, la mayoría no. El espacio de nombres también admite varias llamadas a métodos simples que realizan sofisticadas funciones de E/S de archivos.
Buenas y malas noticias
Todas las plataformas compatibles con Xamarin.Forms admiten el almacenamiento local de la aplicación que es privado para la aplicación.
Las bibliotecas de Xamarin.iOS y Xamarin.Android incluyen una versión de .NET que Xamarin ha adaptado expresamente para estas dos plataformas. Entre ellas se incluyen las clases de System.IO
que puede usar para realizar la E/S de archivos con el almacenamiento local de la aplicación en estas dos plataformas.
Aun así, si busca estas clases System.IO
en un PCL de Xamarin.Forms, no las encontrará. El problema es que Microsoft ha renovado por completo la E/S de archivos para la API de Windows Runtime. Los programas que tienen como destino Windows 8.1, Windows Phone 8.1 y la Plataforma universal de Windows no usan System.IO
para la E/S de archivos.
Esto significa que necesitará usar DependencyService
(que se describe en primer lugar en el Capítulo 9. Llamadas API específicas de la plataforma para implementar E/S de archivos.
Nota:
Las bibliotecas de clases portátiles se han reemplazado por las bibliotecas de .NET Standard 2.0, y .NET Standard 2.0 admite tipos System.IO
para todas las plataformas Xamarin.Forms. Ya no es necesario usar DependencyService
para la mayoría de las tareas de E/S de archivos. Consulte Control de archivos en Xamarin.Forms para obtener un enfoque más moderno para la E/S de archivos.
Primera captura de E/S de archivos entre plataformas
En el ejemplo TextFileTryout se define una interfaz IFileHelper
para la E/S de archivos y las implementaciones de esta interfaz en todas las plataformas. Sin embargo, las implementaciones de Windows Runtime no funcionan con los métodos de esta interfaz porque los métodos de E/S de archivos de Windows Runtime son asincrónicos.
Adaptación de la E/S de archivos de Windows Runtime
Los programas que se ejecutan en Windows Runtime usan clases en los espacios de nombres Windows.Storage
y Windows.Storage.Streams
para la E/S de archivos, incluido el almacenamiento local de la aplicación. Dado que Microsoft ha determinado que cualquier operación que requiera más de 50 milisegundos debe ser asincrónica para evitar el bloqueo del subproceso de interfaz de usuario, estos métodos de E/S de archivos son principalmente asíncronicos.
El código que muestra este nuevo enfoque estará en una biblioteca para que lo puedan usar otras aplicaciones.
Bibliotecas específicas de plataforma
Es ventajoso almacenar código reutilizable en bibliotecas. Esto es obviamente más difícil cuando diferentes partes del código reutilizable son para sistemas operativos completamente diferentes.
En la solución Xamarin.FormsBook.Platform se muestra un enfoque. Esta solución contiene siete proyectos diferentes:
- Xamarin.FormsBook.Platform, un PCL normal de Xamarin.Forms
- Xamarin.FormsBook.Platform.iOS, una biblioteca de clases de iOS
- Xamarin.FormsBook.Platform.Android, una biblioteca de clases de Android
- Xamarin.FormsBook.Platform.UWP, una biblioteca de clases universales de Windows
- Xamarin.FormsBook.Platform.WinRT, un proyecto compartido para el código que es común a todas la plataformas de Windows
Todos los proyectos de plataforma individuales (a excepción de Xamarin.FormsBook.Platform.WinRT) tienen referencias a Xamarin.FormsBook.Platform. Los tres proyectos de Windows tienen una referencia a Xamarin.FormsBook.Platform.WinRT.
Todos los proyectos contienen un método estático Toolkit.Init
para asegurarse de que la biblioteca se carga si un proyecto no hace referencia directa a ella en una solución de aplicación Xamarin.Forms.
El proyecto Xamarin.FormsBook.Platform contiene la nueva interfaz IFileHelper
. Todos los métodos ahora tienen nombres con sufijos Async
y devuelven objetos Task
.
El proyecto Xamarin.FormsBook.Platform.WinRT contiene la clase FileHelper
para Windows Runtime.
El proyectoXamarin.FormsBook.Platform.iOS contiene la clase FileHelper
para iOS. Estos métodos deben ser ahora asincrónicos. Algunos de los métodos usan las versiones asincrónicas de los métodos definidos en StreamWriter
y StreamReader
: WriteAsync
y ReadToEndAsync
. Otros convierten un resultado en un objeto Task
mediante el método FromResult
.
El proyecto Xamarin.FormsBook.Platform.Android contiene una clase FileHelper
similar para Android.
El proyecto Xamarin.FormsBook.Platform también contiene una clase FileHelper
que facilita el uso del objeto DependencyService
.
Para usar estas bibliotecas, una solución de aplicación debe incluir todos los proyectos de la solución Xamarin.FormsBook.Platform, y cada uno de los proyectos de aplicación debe tener una referencia a la biblioteca correspondiente en Xamarin.FormsBook.Platform.
La solución TextFileAsync muestra cómo usar las bibliotecas Xamarin.FormsBook.Platform. Cada uno de los proyectos tiene una llamada a Toolkit.Init
. La aplicación hace uso de las funciones de E/S de archivos asincrónicas.
Mantener en segundo plano
Los métodos de las bibliotecas que realizan llamadas a varios métodos asincrónicos, como los métodos WriteFileAsync
y ReadFileASync
en la clase FileHelper
de Windows Runtime, pueden resultar algo más eficaces mediante el método ConfigureAwait
para evitar volver al subproceso de interfaz de usuario.
No bloquee el subproceso de interfaz de usuario.
A veces, es tentador evitar el uso de ContinueWith
o await
mediante la propiedad Result
en los métodos. Esto se debe evitar para que pueda bloquear el subproceso de interfaz de usuario o incluso bloquear la aplicación.
Métodos propios que admiten await
Puede ejecutar código de forma asincrónica pasándolo a uno de los métodos Task.Run
. Puede llamar a Task.Run
dentro de un método asincrónico que controla parte de la sobrecarga.
A continuación se describen los distintos patrones de Task.Run
.
Conjunto de Mandelbrot básico
Para dibujar el conjunto de Mandelbrot en tiempo real, la biblioteca Xamarin.Forms.Toolkit tiene una estructura Complex
similar a la del espacio de nombres System.Numerics
.
El ejemplo MandelbrotSet tiene un método CalculateMandeblotAsync
en su archivo de código subyacente que calcula el conjunto de Mandelbrot básico en blanco y negro y utiliza BmpMaker
para colocarlo en un mapa de bits.
Marcar el progreso
Para notificar el progreso de un método asincrónico, puede crear una instancia de una clase Progress<T>
y definir el método asincrónico para tener un argumento de tipo IProgress<T>
. Esto se muestra en el ejemplo MandelbrotProgress.
Cancelación del trabajo
También puede escribir un método asincrónico para que sea cancelable. Comienza con una clase denominada CancellationTokenSource
. La propiedad Token
es un valor de tipo CancellationToken
. Esto se pasa a la función asincrónica. Un programa llama al método Cancel
de CancellationTokenSource
(generalmente en respuesta a una acción del usuario) para cancelar la función asincrónica.
El método asincrónico puede comprobar periódicamente la propiedad IsCancellationRequested
de CancellationToken
y salir si la propiedad es true
, o simplemente llamar al método ThrowIfCancellationRequested
, en cuyo caso el método finaliza con OperationCancelledException
.
En el ejemplo MandelbrotCancellation se muestra el uso de una función que se puede cancelar.
Un conjunto Mandelbrot con MVVM
El ejemplo MandelbrotXF tiene una interfaz de usuario más extensa y se basa principal en las clases MandelbrotModel
y MandelbrotViewModel
:
Volver a la Web
La clase WebRequest
usada en algunos ejemplos utiliza un protocolo asincrónico antiguo denominado modelo de programación asincrónica o APM. Puede convertir una clase de este tipo en el protocolo TAP moderno mediante uno de los métodos FromAsync
de la clase TaskFactory
. El ejemplo ApmToTap muestra este comportamiento.