Implementación Async de IValueConverter

Si es un método asíncrono que quiero activar dentro de un IValueConverter.

¿Hay una mejor Espera luego forzando que sea sincrónica llamando al resultado Propiedad?

public async Task Convert(object value, Type targetType, object parameter, string language) { StorageFile file = value as StorageFile; if (file != null) { var image = ImageEx.ImageFromFile(file).Result; return image; } else { throw new InvalidOperationException("invalid parameter"); } } 

Probablemente no desee llamar a Task.Result , por un par de razones.

En primer lugar, como explico en detalle en mi blog, puede un punto muerto a menos que su código async se haya escrito usando ConfigureAwait todas partes. En segundo lugar, probablemente no desee (sincrónicamente) bloquear su UI; sería mejor mostrar temporalmente una “carga …” o una imagen en blanco mientras lee desde el disco, y actualizar cuando finalice la lectura.

Entonces, personalmente, haría de esta parte de mi ViewModel, no un convertidor de valor. Tengo una publicación en el blog que describe algunas formas amigables de enlace de datos para hacer una inicialización asincrónica . Esa sería mi primera elección. Simplemente no se siente bien tener un convertidor de valores iniciando operaciones de fondo asincrónicas.

Sin embargo, si ha considerado su diseño y realmente piensa que un convertidor de valor asincrónico es lo que necesita, entonces tiene que ser un poco inventivo. El problema con los convertidores de valor es que tienen que ser sincrónicos: el enlace de datos comienza en el contexto de datos, evalúa la ruta y luego invoca una conversión de valor. Solo el contexto de datos y el soporte de ruta cambian las notificaciones.

Por lo tanto, debe usar un convertidor de valor (sincrónico) en su contexto de datos para convertir su valor original en un objeto similar a Task vinculante para los datos y luego su enlace de propiedad solo usa una de las propiedades del objeto similar a la Task para obtener el resultado.

Aquí hay un ejemplo de lo que quiero decir:

   

El TextBox es solo un cuadro de entrada. El TextBlock primero establece su propio DataContext en el texto de entrada del TextBox ejecutándolo a través de un convertidor “asincrónico”. TextBlock.Text se establece en el Result de ese convertidor.

El convertidor es bastante simple:

 public class MyAsyncValueConverter : MarkupExtension, IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var val = (string)value; var task = Task.Run(async () => { await Task.Delay(5000); return val + " done!"; }); return new TaskCompletionNotifier(task); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return null; } public override object ProvideValue(IServiceProvider serviceProvider) { return this; } } 

El convertidor primero inicia una operación asíncrona para esperar 5 segundos y luego agrega “¡listo!” hasta el final de la cadena de entrada. El resultado del convertidor no puede ser solo una Task simple porque Task no implementa IPropertyNotifyChanged , entonces estoy usando un tipo que estará en la próxima versión de mi biblioteca AsyncEx . Se ve algo como esto (simplificado para este ejemplo, fuente completa está disponible ):

 // Watches a task and raises property-changed notifications when the task completes. public sealed class TaskCompletionNotifier : INotifyPropertyChanged { public TaskCompletionNotifier(Task task) { Task = task; if (!task.IsCompleted) { var scheduler = (SynchronizationContext.Current == null) ? TaskScheduler.Current : TaskScheduler.FromCurrentSynchronizationContext(); task.ContinueWith(t => { var propertyChanged = PropertyChanged; if (propertyChanged != null) { propertyChanged(this, new PropertyChangedEventArgs("IsCompleted")); if (t.IsCanceled) { propertyChanged(this, new PropertyChangedEventArgs("IsCanceled")); } else if (t.IsFaulted) { propertyChanged(this, new PropertyChangedEventArgs("IsFaulted")); propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage")); } else { propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted")); propertyChanged(this, new PropertyChangedEventArgs("Result")); } } }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, scheduler); } } // Gets the task being watched. This property never changes and is never null. public Task Task { get; private set; } Task ITaskCompletionNotifier.Task { get { return Task; } } // Gets the result of the task. Returns the default value of TResult if the task has not completed successfully. public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } } // Gets whether the task has completed. public bool IsCompleted { get { return Task.IsCompleted; } } // Gets whether the task has completed successfully. public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } } // Gets whether the task has been canceled. public bool IsCanceled { get { return Task.IsCanceled; } } // Gets whether the task has faulted. public bool IsFaulted { get { return Task.IsFaulted; } } // Gets the error message for the original faulting exception for the task. Returns null if the task is not faulted. public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } } public event PropertyChangedEventHandler PropertyChanged; } 

Al unir estas piezas, creamos un contexto de datos asincrónico que es el resultado de un convertidor de valor. El contenedor de Task amigable para Task vinculación de datos simplemente usará el resultado predeterminado (normalmente null o 0 ) hasta que la Task finalice. Por lo tanto, el Result del contenedor es bastante diferente de Task.Result : no se bloqueará sincrónicamente y no hay peligro de interlocking.

Pero para reiterar: elegiría poner lógica asíncrona en ViewModel en lugar de un convertidor de valor.