【问题标题】:Async implementation of IValueConverterIValueConverter 的异步实现
【发布时间】:2013-02-21 13:44:49
【问题描述】:

我有一个异步方法,我想在 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");
    }
}

【问题讨论】:

    标签: c# windows-runtime async-await c#-5.0 winrt-async


    【解决方案1】:

    出于几个原因,您可能不想致电 Task.Result

    首先,正如我在我的博客中详细解释的那样,you can deadlock 除非您的 async 代码已在任何地方使用 ConfigureAwait 编写。其次,您可能不想(同步)阻止您的 UI;最好在从磁盘读取时暂时显示“正在加载...”或空白图像,并在读取完成时更新。

    所以,就我个人而言,我会将这部分作为我的 ViewModel,而不是值转换器。我有一篇博客文章描述了一些databinding-friendly ways to do asynchronous initialization。那将是我的第一选择。让 值转换器 启动异步后台操作感觉不妥。

    但是,如果您已经考虑过自己的设计,并且真的认为异步值转换器是您所需要的,那么您必须要有一点创造性。值转换器的问题在于它们必须是同步的:数据绑定从数据上下文开始,评估路径,然后调用值转换。只有数据上下文和路径支持更改通知。

    因此,您必须在数据上下文中使用(同步)值转换器将原始值转换为数据绑定友好的 Task 类对象,然后您的属性绑定只使用 @987654330 上的属性之一@-like 对象获取结果。

    这是我的意思的一个例子:

    <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 library 的下一个版本中出现。它看起来像这样(此示例已简化;full source is available):

    // 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 包装器将只使用默认结果(通常是null0),直到Task 完成。所以wrapper的ResultTask.Result有很大的不同:不会同步阻塞,也没有死锁的危险。

    但重申一下:我会选择将异步逻辑放入 ViewModel 而不是值转换器。

    【讨论】:

    • 您好,感谢您的回复。在 viewmodel 中进行异步操作确实是我目前作为解决方法的解决方案。但是这个感觉很好。有一些问题我觉得他们在转换器中是正确的。我希望我忽略了 IAsyncValueConverter 之类的东西。但似乎没有这样的事情:-(。将您的帖子标记为答案,因为我认为它会帮助其他有同样问题的人:-)
    • 很好,但我想问你一个问题:为什么转换器应该扩展MarkupExtension 以及为什么ProvideValue 会返回自身?
    • @Alberto:这只是一种 XAML 便利,因此您不必在资源字典中声明全局实例并从标记中引用它。
    • @StephenCleary,ITaskCompletionNotifier 的显式实现的目的是什么?我在任何地方都看不到您的答案中的定义。
    • 我的答案中的代码是我的答案中链接的代码的简化。显式实现是因为我有一个从ITaskCompletionNotifier 派生的通用ITaskCompletionNotifier&lt;T&gt;。有关更完整的示例,请参阅我的 MSDN article on async data binding
    【解决方案2】:

    另一种方法是制作支持异步源或数据的自己的控件。

    这是带有图像的示例

        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;
            }
        }
    }
    

    此外,您可以在图像上实现加载活动指示器,直到任务得到解决。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-31
      • 2013-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多