Подтвердить что ты не робот

Асинхронное внедрение IValueConverter

Если асинхронный метод, который я хочу вызвать внутри IValueConverter.

Есть ли лучший Wait, а затем принудительно его синхронно, вызывая результат 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");
    }
}
4b9b3361

Ответ 1

Вероятно, вы не хотите вызывать Task.Result по нескольким причинам.

Во-первых, поскольку я подробно объясняю свой блог, вы можете зайти в тупик, если ваш код async не был написан с помощью ConfigureAwait везде. Во-вторых, вы, вероятно, не хотите (синхронно) блокировать свой пользовательский интерфейс; было бы лучше временно показать "загрузку..." или пустое изображение при чтении с диска и обновить, когда чтение будет завершено.

Итак, лично я бы сделал эту часть своего ViewModel, а не преобразователя значений. У меня есть сообщение в блоге с описанием дружественных к базе данных способов для асинхронной инициализации. Это был бы мой первый выбор. Просто не кажется правильным, чтобы преобразователь значений начинал выполнять асинхронные фоновые операции.

Однако, если вы рассмотрели свой дизайн и действительно думаете, что конвертер асинхронных значений - это то, что вам нужно, тогда вам нужно немного изобретать. Проблема со значениями преобразователей заключается в том, что они должны быть синхронными: привязка данных начинается в контексте данных, оценивает путь и затем вызывает преобразование значений. Только уведомления об изменениях данных и пути поддерживают уведомления об изменениях.

Итак, вы должны использовать (синхронный) конвертер значений в контексте данных, чтобы преобразовать исходное значение в дружественный databinding Task -подобный объект, а затем привязка вашего свойства использует только одно из свойств на Task -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 секунд, а затем добавить "done!" до конца входной строки. Результат конвертера не может быть просто простым 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;
}

Объединив эти фрагменты, мы создали асинхронный контекст данных, который является результатом преобразователя значений. Дружественный databinding Task -сервер будет использовать результат по умолчанию (обычно null или 0) до тех пор, пока Task не завершится. Таким образом, оболочка Result сильно отличается от Task.Result: она не будет блокироваться синхронно и нет опасности тупика.

Но повторить: я бы поставил асинхронную логику в ViewModel, а не в конвертер значений.