IValueConverter 的异步实现 [英] Async implementation of IValueConverter

查看:24
本文介绍了IValueConverter 的异步实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个异步方法,我想在 IValueConverter 中触发它.

I have an asynchronous method which I want to trigger inside an IValueConverter.

有没有比通过调用 Result 属性强制它同步更好的方法?

Is there a better way than forcing it to be synchronous by calling the Result property?

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,原因有几个.

You probably don't want to call Task.Result, for a couple of reasons.

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

Firstly, as I explain in detail on my blog, you can deadlock unless your async code is has been written using ConfigureAwait everywhere. Secondly, you probably don't want to (synchronously) block your UI; it would be better to temporarily show a "loading..." or blank image while reading from the disk, and update when the read completes.

因此,就我个人而言,我会将这部分作为我的 ViewModel,而不是值转换器.我有一篇博客文章描述了一些数据绑定友好的异步初始化方法.那将是我的第一选择.让值转换器启动异步后台操作,感觉不太对.

So, personally, I would make this part of my ViewModel, not a value converter. I have a blog post describing some databinding-friendly ways to do asynchronous initialization. That would be my first choice. It just doesn't feel right to have a value converter kicking off asynchronous background operations.

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

However, if you've considered your design and really think an asynchronous value converter is what you need, then you have to get a bit inventive. The problem with value converters is that they have to be synchronous: the data binding starts at the data context, evaluates the path, and then invokes a value conversion. Only the data context and path support change notifications.

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

So, you have to use a (synchronous) value converter in your data context to convert your original value into a databinding-friendly Task-like object and then your property binding just uses one of the properties on the Task-like object to get the result.

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

Here's an example of what I mean:

<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.

The TextBox is just an input box. The TextBlock first sets its own DataContext to the TextBox's input text running it through an "asynchronous" converter. TextBlock.Text is set to the Result of that converter.

转换器非常简单:

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 库 的下一个版本中.它看起来像这样(这个例子简化了;完整源代码可用):

The converter first starts an asynchronous operation to wait 5 seconds and then add " done!" to the end of the input string. The result of the converter can't be just a plain Task because Task doesn't implement IPropertyNotifyChanged, so I'm using a type that will be in the next release of my AsyncEx library. It looks something like this (simplified for this example; 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 完成.所以包装器的 ResultTask.Result 完全不同:它不会同步阻塞,也没有死锁的危险.

By putting these pieces together, we've created an asynchronous data context that is the result of a value converter. The databinding-friendly Task wrapper will just use the default result (usually null or 0) until the Task completes. So the wrapper's Result is quite different than Task.Result: it won't synchronously block and there's no danger of deadlock.

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

But to reiterate: I'd choose to put asynchronous logic into the ViewModel rather than a value converter.

这篇关于IValueConverter 的异步实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆