asynchronous实现IValueConverter
如果我想在一个IValueConverter内部触发asynchronous方法。
有没有更好的等待,然后强制它通过调用结果属性同步?
public async Task<object> 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"); } }
你可能不想调用Task.Result
,有几个原因。
首先,正如我在我的博客上详细解释的那样,除非您的async
代码是使用ConfigureAwait
编写的,否则您可能会死锁 。 其次,你可能不想(同步)阻止你的用户界面; 从磁盘读取数据时暂时显示“加载…”或空白图像,并在读取完成时更新。
所以,我个人认为,这是ViewModel的一部分,而不是一个值转换器。 我有一篇博客文章描述了一些数据绑定友好的方法来做asynchronous初始化 。 那将是我的第一select。 让一个值转换器启动asynchronous后台操作是不正确的。
但是,如果你已经考虑过你的devise,并且真的认为你需要一个asynchronous值转换器,那么你必须有点创新。 值转换器的问题是它们必须是同步的:数据绑定从数据上下文开始,评估path,然后调用值转换。 只有数据上下文和path支持才能更改通知。
所以,你必须在你的数据上下文中使用一个(同步的)值转换器来将你的原始值转换成一个数据绑定友好的Task
类对象,然后你的属性绑定只是使用Task
类对象上的一个属性来获得结果。
这里是我的意思的一个例子:
<TextBox Text="" Name="Input"/> <TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}" Text="{Binding Path=Result}"/>
TextBox
只是一个input框。 TextBlock
首先将自己的DataContext
设置为通过“asynchronous”转换器运行的TextBox
input文本。 TextBlock.Text
被设置为该转换器的Result
。
转换器非常简单:
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<string>(task); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return null; } public override object ProvideValue(IServiceProvider serviceProvider) { return this; } }
转换器首先启动asynchronous操作等待5秒,然后添加“完成! 到inputstring的末尾。 转换器的结果不能只是一个普通的Task
因为Task
没有实现IPropertyNotifyChanged
,所以我使用的将是在我的AsyncEx库的下一个版本的types 。 它看起来像这样(这个例子简化, 完整的源代码可用 ):
// Watches a task and raises property-changed notifications when the task completes. public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged { public TaskCompletionNotifier(Task<TResult> 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 <c>null</c>. public Task<TResult> 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 <c>null</c> if the task is not faulted. public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } } public event PropertyChangedEventHandler PropertyChanged; }
通过将这些部分放在一起,我们创build了一个asynchronous数据上下文,这是数值转换器的结果。 数据绑定友好的Task
包装只会使用默认结果(通常为null
或0
),直到Task
完成。 所以包装的Result
与Task.Result
完全不同,它不会同步阻塞,也没有死锁的危险。
但是要重申:我会select将asynchronous逻辑放入ViewModel而不是一个值转换器。