的IValueConverter的异步执行 [英] Async Implementation of IValueConverter

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

问题描述

如果我想要一个异步方法触发的IValueConverter内。

有没有更好的通过调用结果,然后等待迫使它是同步的房产吗?

 公共异步任务<对象>转换(对象的值,类型TARGETTYPE,对象参数,字符串语言)
{
    StorageFile文件=值StorageFile;    如果(文件!= NULL)
    {
        VAR图像= ImageEx.ImageFromFile(文件)。结果;
        返回形象;
    }
    其他
    {
        抛出新的InvalidOperationException异常(无效参数);
    }
}


解决方案

您可能不想叫 Task.Result ,一对夫妇的原因。

首先,正如我在细节上我的博客解释,你可以死锁除非你的异步 code为已使用 ConfigureAwait 到处写的。其次,你可能不希望(同步)阻止你的用户界面;倒不如暂时显示载入中...或在从磁盘读取空白图像,并更新读完成时。

所以,就个人而言,我会做我的视图模型的这部分,而不是一个值转换器。我有一个博客帖子描述了一些绑定友好的方式进行异步初始化。这将是我的第一选择。它只是感觉不对拥有的值转换的开球异步后台操作。

不过,如果你已经考虑您的设计和真的觉得一个异步值转换器是你所需要的,那么你必须得有点创造性。同值的转换器的问题在于,它们有无的是同步:该数据在数据上下文结合开始,评估的路径,然后调用一个值变换。只有数据上下文和路径支持更改通知。

所以,你必须使用一个(同步)值转换器在您的数据环境到原来的值转换为数据绑定友好工作般的对象,然后你的财产结合只是使用的属性之一的工作状物体得到的结果。

下面是我的意思的例子:

 <文本框的文本=NAME =输入/>
< TextBlock中的DataContext ={绑定的ElementName =输入,路径=文本,转换器= {本地:MyAsyncValueConverter}}
           文本={绑定路径=结果}/>

文本框只是一个输入框。在的TextBlock 首先设置自己的的DataContext 文本框的输入文本通过异步转换器运行它。 TextBlock.Text 设置为结果的转换器。

该转换器是pretty简单:

 公共类MyAsyncValueConverter:的MarkupExtension,的IValueConverter
{
    公共对象转换(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
    {
        VAR VAL =(字符串)值;
        VAR任务= Task.Run(异步()=>
        {
            等待Task.Delay(5000);
            返回VAL +完成了!
        });
        返回新TaskCompletionNotifier<串GT;(任务);
    }    公共对象ConvertBack(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
    {
        返回null;
    }    公众覆盖对象ProvideValue(的IServiceProvider的ServiceProvider)
    {
        返回此;
    }
}

该转换器首先启动一个异步操作以等待5秒钟,然后添加完成了!于输入串的结尾。转换器的结果不能只是一个普通的工作,因为工作不执行 IPropertyNotifyChanged ,所以我使用一个类型,这将是我的 AsyncEx库。它看起来像这样(简化这个例子中,<一个href=\"http://nitoasyncex.$c$cplex.com/SourceControl/changeset/view/7776f08613e7#Source/Nito.AsyncEx%20%28NET4,%20Win8,%20SL4,%20WP75%29/TaskCompletionNotifier%20%28of%20TResult%29.cs\">full源可用):

  //手表任务和任务完成时引发属性更改通知。
公共密封类TaskCompletionNotifier&LT; TResult&GT; :INotifyPropertyChanged的
{
    公共TaskCompletionNotifier(任务&LT; TResult&GT;任务)
    {
        任务=任务;
        如果(!task.IsCompleted)
        {
            VAR调度=(SynchronizationContext.Current == NULL)? TaskScheduler.Current:TaskScheduler.FromCurrentSynchronizationContext();
            task.ContinueWith(T =&GT;
            {
                VAR的PropertyChanged =的PropertyChanged;
                如果(的PropertyChanged!= NULL)
                {
                    的PropertyChanged(这一点,新PropertyChangedEventArgs(IsCompleted));
                    如果(t.IsCanceled)
                    {
                        的PropertyChanged(这一点,新PropertyChangedEventArgs(IsCanceled));
                    }
                    否则,如果(t.IsFaulted)
                    {
                        的PropertyChanged(这一点,新PropertyChangedEventArgs(IsFaulted));
                        的PropertyChanged(这一点,新PropertyChangedEventArgs(的ErrorMessage));
                    }
                    其他
                    {
                        的PropertyChanged(这一点,新PropertyChangedEventArgs(IsSuccessfullyCompleted));
                        的PropertyChanged(这一点,新PropertyChangedEventArgs(结果));
                    }
                }
            },
            CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            调度程序);
        }
    }    //获取被监视的任务。此属性不会改变,并且永远不会&LT; C&GT;空&LT; / c取代。
    公共任务&LT; TResult&GT;任务{搞定;私人集; }    任务ITaskCompletionNotifier.Task
    {
        {返回任务; }
    }    //获取任务的结果。返回TResult的默认值,如果任务没有成功完成。
    公共TResult结果{{返回(Task.Status == TaskStatus.RanToCompletion)? Task.Result:默认(TResult); }}    //获取任务是否已经完成。
    公共BOOL IsCompleted {{返回Task.IsCompleted; }}    //获取任务是否已成功完成。
    公共BOOL IsSuccessfullyCompleted {{返回Task.Status == TaskStatus.RanToCompletion; }}    //获取任务是否已被取消。
    公共BOOL IsCanceled {{返回Task.IsCanceled; }}    //获取任务是否出现了故障。
    公共BOOL IsFaulted {{返回Task.IsFaulted; }}    //获取任务的原断层异常的错误消息。返回&LT; C&GT;空&LT; / c取代;如果任务没有出现故障。
    公共字符串的ErrorMessage {{返回(的InnerException == NULL)?空:InnerException.Message; }}    公共事件PropertyChangedEventHandler的PropertyChanged;
}

通过将这些碎片拼凑起来,我们已经创建了一个价值转换的结果,异步数据上下文。数据绑定友好工作包装将只使用默认结果(通常为 0 ),直到工作完成。因此,包装的结果 Task.Result 很大的不同:它不会阻止同步并没有陷入僵局的危险

但重申:我会选择把异步逻辑到视图模型,而不是一个值转换器

If an async Method which I want to trigger inside a IValueConverter.

Is there a better Wait then 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");
    }
}

解决方案

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

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.

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.

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}"/>

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.

The converter is pretty simple:

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

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

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.

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

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

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