En el marco de Caliburn.Micro, IResult y las corrutinas proporcionan un enfoque eficaz para la programación asíncrona y basada en tareas. Las corrutinas son componentes que extienden las subrutinas, permitiendo múltiples puntos de suspensión y reanudación durante la ejecución. Esta técnica facilita la secuenciación de operaciones complejas, como llamadas a servciios web o interacciones de usuario, de manera declarativa y legible.
Para utilizar esta funcionalidad en Caliburn.Micro, es necesario implementar la interfaz IResult en una clase que represente la tarea deseada, y devolver instancias de IResult mediante yield dentro de una acción con retorno IEnumerable. Esto permite combinar tareas síncronas y asíncronas en un flujo secuencial. Por ejemplo, en una aplicación Silverlight que necesita cargar contenido dinámicamente, la acción podría mostrar un indicador de progreso, descargar un paquete externo, ocultar el indicador y navegar a una pantalla específica.
using System.Collections.Generic; using System.ComponentModel.Composition;
[Export(typeof(PrimeraPantallaViewModel))] public class PrimeraPantallaViewModel { public IEnumerable<IResult> IniciarNavegacion() { yield return IndicadorCarga.Mostrar("Procesando..."); yield return new DescargadorPaquete("MiApp.Modulo.Externo.xap"); yield return IndicadorCarga.Ocultar(); yield return new MostrarPantalla("PantallaExterna"); } }
</div>En el ejemplo anterior, el método IniciarNavegacion devuelve IEnumerable, lo que es esencial para las corrutinas. Cada yield devuelve una instancia de IResult, representando una tarea individual. El compilador pausa la ejecución después de cada yield hasta que la tarea se completa, manteniendo un flujo lógico y fácil de seguir. Para entender la implementación, revise la interfaz IResult:
<div>```
public interface IResult
{
void Ejecutar(ContextoEjecucionCorrutina contexto);
event EventHandler<ArgumentosCompletadoResultado> Completado;
}
public class ContextoEjecucionAccion { public MensajeAccion Mensaje; public FrameworkElement Fuente; public object ArgumentosEvento; public object Destino; public DependencyObject Vista; public MethodInfo Metodo; public Func PuedeEjecutar; public object this[string clave]; }
</div>Basado en este contexto, se pueden crear implementaciones reutilizables de IResult. Por ejemplo, un indicador de carga que busca un BusyIndicator en el árbol visual:
<div>```
using System;
using System.Windows;
using System.Windows.Controls;
public class IndicadorCarga : IResult
{
private readonly string mensaje;
private readonly bool ocultar;
public IndicadorCarga(string texto)
{
mensaje = texto;
}
public IndicadorCarga(bool ocultarFlag)
{
ocultar = ocultarFlag;
}
public void Ejecutar(ContextoEjecucionCorrutina contexto)
{
var elementoVista = contexto.Vista as FrameworkElement;
while (elementoVista != null)
{
var indicador = elementoVista as BusyIndicator;
if (indicador != null)
{
if (!string.IsNullOrEmpty(mensaje))
indicador.BusyContent = mensaje;
indicador.IsBusy = !ocultar;
break;
}
elementoVista = elementoVista.Parent as FrameworkElement;
}
Completado(this, new ArgumentosCompletadoResultado());
}
public event EventHandler<ArgumentosCompletadoResultado> Completado = delegate { };
public static IResult Mostrar(string texto = null)
{
return new IndicadorCarga(texto);
}
public static IResult Ocultar()
{
return new IndicadorCarga(true);
}
}
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq;
public class DescargadorPaquete : IResult { private static readonly Dictionary<string, DeploymentCatalog> Catalogos = new Dictionary<string, DeploymentCatalog>(); private readonly string uri;
[Import]
public AggregateCatalog Catalogo { get; set; }
public DescargadorPaquete(string rutaRelativa)
{
uri = rutaRelativa;
}
public void Ejecutar(ContextoEjecucionCorrutina contexto)
{
DeploymentCatalog catalogo;
if (Catalogos.TryGetValue(uri, out catalogo))
{
Completado(this, new ArgumentosCompletadoResultado());
return;
}
catalogo = new DeploymentCatalog(uri);
catalogo.DownloadCompleted += (sender, args) =>
{
if (args.Error == null)
{
Catalogos[uri] = catalogo;
Catalogo.Catalogs.Add(catalogo);
var ensambladosNuevos = catalogo.Parts
.Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
.Where(asm => !AssemblySource.Instance.Contains(asm));
foreach (var asm in ensambladosNuevos)
AssemblySource.Instance.Add(asm);
}
else
{
IndicadorCarga.Ocultar().Ejecutar(contexto);
}
Completado(this, new ArgumentosCompletadoResultado
{
Error = args.Error,
FueCancelado = false
});
};
catalogo.DownloadAsync();
}
public event EventHandler<ArgumentosCompletadoResultado> Completado = delegate { };
}
</div>La clase ArgumentosCompletadoResultado contiene propiedades como Error y FueCancelado, que permiten controlar el flujo de la corrutina. Si ocurre un error o se cancela, Caliburn.Micro detiene la ejecución. Esto es útil para simplificar lógica en diálogos o operaciones condicionales.
Para acciones más específicas, como mostrar pantallas, se pueden crear IResult que dependan de servicios de la aplicación. Al usar la inyección de dependencias, Caliburn.Micro pasa las instancias necesarias antes de ejecutar la tarea:
<div>```
using System;
using System.ComponentModel.Composition;
public class MostrarPantalla : IResult
{
private readonly Type tipoPantalla;
private readonly string nombre;
[Import]
public IShell CapaPrincipal { get; set; }
public MostrarPantalla(string nombrePantalla)
{
nombre = nombrePantalla;
}
public MostrarPantalla(Type tipo)
{
tipoPantalla = tipo;
}
public void Ejecutar(ContextoEjecucionCorrutina contexto)
{
var pantalla = !string.IsNullOrEmpty(nombre)
? IoC.Get<object>(nombre)
: IoC.GetInstance(tipoPantalla, null);
CapaPrincipal.ActivateItem(pantalla);
Completado(this, new ArgumentosCompletadoResultado());
}
public event EventHandler<ArgumentosCompletadoResultado> Completado = delegate { };
public static MostrarPantalla De<T>()
{
return new MostrarPantalla(typeof(T));
}
}