[英]Async implementation of IValueConverter
我有一個異步方法,我想在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");
}
}
您可能不想調用Task.Result
,原因有幾個。
首先,正如我在我的博客中詳細解釋的那樣,除非您的async
代碼是在任何地方使用ConfigureAwait
編寫的,否則您可能會死鎖。 其次,您可能不想(同步)阻止您的 UI; 最好在從磁盤讀取時臨時顯示“正在加載...”或空白圖像,並在讀取完成時更新。
因此,就我個人而言,我會將這部分作為我的 ViewModel,而不是值轉換器。 我有一篇博客文章描述了一些數據綁定友好的異步初始化方法。 那將是我的第一選擇。 讓值轉換器啟動異步后台操作感覺不太對。
但是,如果您已經考慮過您的設計並且真的認為異步值轉換器是您所需要的,那么您必須要有一點創造性。 值轉換器的問題在於它們必須是同步的:數據綁定從數據上下文開始,評估路徑,然后調用值轉換。 只有數據上下文和路徑支持更改通知。
因此,您必須在數據上下文中使用(同步)值轉換器將原始值轉換為數據綁定友好的類Task
對象,然后您的屬性綁定僅使用類Task
對象上的屬性之一來獲取結果。
這是我的意思的一個例子:
<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
Text="{Binding Path=Result}"/>
TextBox
只是一個輸入框。 TextBlock
首先將自己的DataContext
設置為TextBox
的輸入文本,通過“異步”轉換器運行它。 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;
}
}
轉換器首先啟動一個異步操作等待5秒,然后添加“完成!” 到輸入字符串的末尾。 轉換器的結果不能只是一個普通的Task
因為Task
沒有實現IPropertyNotifyChanged
,所以我使用的類型將出現在我的AsyncEx 庫的下一個版本中。 它看起來像這樣(針對此示例進行了簡化;提供完整源代碼):
// 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;
}
通過將這些部分放在一起,我們創建了一個異步數據上下文,它是值轉換器的結果。 數據綁定友好的Task
包裝器將只使用默認結果(通常為null
或0
),直到Task
完成。 所以包裝器的Result
與Task.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;
}
}
}
此外,您可以在圖像上實現加載活動指示器,直到任務得到解決。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.