IValueConverter的异步实现

Boa*_*ler 23 c# async-await c#-5.0 windows-runtime winrt-async

如果我想在IValueConverter中触发一个异步方法.

有没有更好的等待,然后通过调用结果属性强制它同步?

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 43

Task.Result出于几个原因,你可能不想打电话.

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

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

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

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

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

<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
           Text="{Binding Path=Result}"/>
Run Code Online (Sandbox Code Playgroud)

TextBox只是一个输入框.在TextBlock第一设置自己的DataContextTextBox运行它通过'异步’转换器的输入文本.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

转换器首先启动异步操作等待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;
}
Run Code Online (Sandbox Code Playgroud)

通过将这些部分放在一起,我们创建了一个异步数据上下文,它是值转换器的结果.数据绑定友好的Task包装器将使用默认结果(通常null0)直到Task完成.所以包装器ResultTask.Result它完全不同:它不会同步阻塞,也没有死锁的危险.

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

  • @Alberto:这只是XAML的便利,因此您不必在资源字典中声明全局实例并从标记中引用它. (2认同)