C# IValueConverter的异步实现
我想在C# IValueConverter的异步实现,c#,windows-runtime,async-await,c#-5.0,winrt-async,C#,Windows Runtime,Async Await,C# 5.0,Winrt Async,我想在IValueConverter中触发一个异步方法 有没有比通过调用Result属性强制同步更好的方法 public async Task<object> Convert(object value, Type targetType, object parameter, string language) { StorageFile file = value as StorageFile; if (file != null) { var im
IValueConverter
中触发一个异步方法
有没有比通过调用Result
属性强制同步更好的方法
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");
}
}
公共异步任务转换(对象值、类型targetType、对象参数、字符串语言)
{
StorageFile=值为StorageFile;
如果(文件!=null)
{
var image=ImageEx.ImageFromFile(file.Result);
返回图像;
}
其他的
{
抛出新的InvalidOperationException(“无效参数”);
}
}
出于几个原因,您可能不想调用Task.Result
首先,正如我在我的博客上详细解释的,除非您的async
代码是使用configurewait
编写的。其次,您可能不想(同步地)阻塞您的UI;从磁盘读取时最好临时显示“正在加载…”或空白图像,并在读取完成时更新
所以,就我个人而言,我会把这部分作为我的ViewModel的一部分,而不是一个值转换器。我有一篇博客文章描述了一些。那将是我的第一选择。让值转换器启动异步后台操作是不对的
然而,如果你已经考虑过你的设计,并且真的认为异步值转换器是你所需要的,那么你必须有点创造性。值转换器的问题是它们必须是同步的:数据绑定从数据上下文开始,计算路径,然后调用值转换。只有数据上下文和路径支持更改通知
因此,您必须在数据上下文中使用(同步)值转换器将原始值转换为数据绑定友好的任务
-类对象,然后您的属性绑定只使用任务
-类对象上的一个属性来获得结果
这里有一个例子来说明我的意思:
<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
Text="{Binding Path=Result}"/>
转换器首先启动异步操作以等待5秒,然后在输入字符串的末尾添加“done!”。转换器的结果不能仅仅是一个普通的任务
,因为任务
没有实现IPropertyNotifyChanged
,所以我使用的类型将出现在我的下一个版本中。它看起来像这样(本例简化了;):
//监视任务并在任务完成时引发属性更改通知。
公共密封类TaskCompletionNotifier:INotifyPropertyChanged
{
公共任务完成通知程序(任务)
{
任务=任务;
如果(!task.IsCompleted)
{
var scheduler=(SynchronizationContext.Current==null)?TaskScheduler.Current:TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(t=>
{
var propertyChanged=propertyChanged;
if(propertyChanged!=null)
{
房地产变更(这是指新的房地产变更开发项目(“已完成”);
如果(t.IsCanceled)
{
房地产变更(这是指新的房地产变更数据(“已取消”);
}
否则,如果(t.IsFaulted)
{
propertyChanged(这是新的PropertyChangedEventArgs(“IsFaulted”);
propertyChanged(这是新的PropertyChangedEventArgs(“ErrorMessage”);
}
其他的
{
房地产变更(此,新的房地产变更已完成(“IsSuccessfullyCompleted”);
propertyChanged(此,新PropertyChangedEventArgs(“结果”);
}
}
},
取消令牌。无,
TaskContinuationOptions.Executes同步执行,
调度程序);
}
}
//获取正在监视的任务。此属性从不更改,也从不为null。
公共任务任务{get;private set;}
任务ITaskCompletionNotifier.Task
{
获取{return Task;}
}
//获取任务的结果。如果任务未成功完成,则返回TResult的默认值。
公共TResult结果{get{return(Task.Status==TaskStatus.RanToCompletion)?Task.Result:default(TResult);}
//获取任务是否已完成。
public bool IsCompleted{get{return Task.IsCompleted;}
//获取任务是否已成功完成。
public bool IsSuccessfullyCompleted{get{return Task.Status==TaskStatus.RanToCompletion;}
//获取任务是否已取消。
public bool IsCanceled{get{return Task.IsCanceled;}
//获取任务是否出现错误。
public bool出现故障{get{return Task.IsFaulted;}}
//获取任务的原始故障异常的错误消息。如果任务没有故障,则返回null。
公共字符串ErrorMessage{get{return(InnerException==null)?null:InnerException.Message;}
公共事件属性更改事件处理程序属性更改;
}
通过将这些部分放在一起,我们创建了一个异步数据上下文,它是值转换器的结果。数据绑定友好的任务
包装器将只使用默认结果(通常为null
或0
),直到任务
完成。因此,包装器的结果
与任务.Result
大不相同:它不会同步阻塞,也不会出现死锁的危险
但要重申:我会选择将异步逻辑放入ViewModel,而不是值转换器。另一种方法是创建支持异步源或数据的控件 下面是带有图像的示例
public class AsyncSourceCachedImage : CachedImage
{
public static BindableProperty AsyncSourceProperty = BindableProperty.Create(nameof(AsyncSource), typeof(Task<Xamarin.Forms.ImageSource>), typeof(AsyncSourceSvgCachedImage), null, propertyChanged: SourceAsyncPropertyChanged);
public Task<Xamarin.Forms.ImageSource> AsyncSource
{
get { return (Task<Xamarin.Forms.ImageSource>)GetValue(AsyncSourceProperty); }
set { SetValue(AsyncSourceProperty, value); }
}
private static async void SourceAsyncPropertyChanged(BindableObject bindable, object oldColor, object newColor)
{
var view = bindable as AsyncSourceCachedImage;
var taskForImageSource = newColor as Task<Xamarin.Forms.ImageSource>;
if (taskForImageSource != null)
{
var awaitedImageSource = await taskForImageSource;
view.Source = awaitedImageSource;
}
}
}
公共类AsyncSourceCachedImage:CachedImage
{
公共静态BindableProperty异步源
// 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;
}
public class AsyncSourceCachedImage : CachedImage
{
public static BindableProperty AsyncSourceProperty = BindableProperty.Create(nameof(AsyncSource), typeof(Task<Xamarin.Forms.ImageSource>), typeof(AsyncSourceSvgCachedImage), null, propertyChanged: SourceAsyncPropertyChanged);
public Task<Xamarin.Forms.ImageSource> AsyncSource
{
get { return (Task<Xamarin.Forms.ImageSource>)GetValue(AsyncSourceProperty); }
set { SetValue(AsyncSourceProperty, value); }
}
private static async void SourceAsyncPropertyChanged(BindableObject bindable, object oldColor, object newColor)
{
var view = bindable as AsyncSourceCachedImage;
var taskForImageSource = newColor as Task<Xamarin.Forms.ImageSource>;
if (taskForImageSource != null)
{
var awaitedImageSource = await taskForImageSource;
view.Source = awaitedImageSource;
}
}
}