2014-01-06 4 views
15

Теперь я знаю, что свойства не поддерживают async/ждут по уважительным причинам. Но иногда вам нужно запустить некоторую дополнительную фоновую обработку из средства настройки свойств - хорошим примером является привязка данных в сценарии MVVM.Правильный способ вызова методов async из набора свойств привязки данных?

В моем случае у меня есть свойство, связанное с SelectedItem ListView. Разумеется, я сразу же установил новое значение в поле поддержки и выполнил основную работу свойства. Но изменение выбранного элемента в пользовательском интерфейсе также должно инициировать вызов службы REST для получения новых данных на основе выбранного элемента.

Поэтому мне нужно вызвать метод async. Я не могу дождаться этого, очевидно, но я также не хочу запускать и забывать вызов, поскольку я мог бы пропустить исключения во время обработки асинхронных сообщений.

Теперь мои взять следующий:

private Feed selectedFeed; 
public Feed SelectedFeed 
{ 
    get 
    { 
     return this.selectedFeed; 
    } 
    set 
    { 
     if (this.selectedFeed != value) 
     { 
      this.selectedFeed = value; 
      RaisePropertyChanged(); 

      Task task = GetFeedArticles(value.Id); 

      task.ContinueWith(t => 
       { 
        if (t.Status != TaskStatus.RanToCompletion) 
        { 
         MessengerInstance.Send<string>("Error description", "DisplayErrorNotification"); 
        } 
       }); 
     } 
    } 
} 

ИТАК кроме того, я мог бы выйти манипуляционный из сеттера к синхронному методу, это правильный способ справиться с таким сценарием? Есть ли лучшее, менее захламленное решение, которого я не вижу?

Было бы очень интересно увидеть, как некоторые другие принимают эту проблему. Мне немного любопытно, что мне не удалось найти других обсуждений по этой конкретной теме, поскольку мне кажется очень распространенным я в приложениях MVVM, которые сильно используют привязку данных.

+0

забавная вещь, чтобы быть осторожным здесь обработки, когда изменения свойств в то время как запрос REST находится в стадии разработки. Тем более, что вам не гарантировано, что они будут завершены в том порядке, в котором они были вызваны. –

+0

Да, это правда :), но эта проблема возникла бы независимо от того, использую ли я средство настройки свойств для запуска вызова REST или события или чего-то еще. Мое занятие - отменить все выполняемые запросы, когда выбранный элемент изменяется до отправки нового. –

+0

Вместо этого вы можете приложить команду к событию с измененным изменением, а не проверять, был ли выбранный элемент изменен пользовательским интерфейсом. –

ответ

10

У меня есть NotifyTaskCompletion type in my AsyncEx library, который по существу является оберткой INotifyPropertyChanged для Task/Task<T>. AFAIK в настоящее время доступно очень мало информации о async в сочетании с MVVM, поэтому дайте мне знать, если вы найдете какие-либо другие подходы.

В любом случае, подход NotifyTaskCompletion работает лучше всего, если ваши задачи возвращают результаты. I.e., из вашего текущего примера кода выглядит, что GetFeedArticles устанавливает свойства, связанные с данными, как побочный эффект вместо возврата статей. Если вы сделаете это возвращение Task<T> вместо этого, вы можете закончить с помощью следующего кода:

private Feed selectedFeed; 
public Feed SelectedFeed 
{ 
    get 
    { 
    return this.selectedFeed; 
    } 
    set 
    { 
    if (this.selectedFeed == value) 
     return; 
    this.selectedFeed = value; 
    RaisePropertyChanged(); 
    Articles = NotifyTaskCompletion.Create(GetFeedArticlesAsync(value.Id)); 
    } 
} 

private INotifyTaskCompletion<List<Article>> articles; 
public INotifyTaskCompletion<List<Article>> Articles 
{ 
    get { return this.articles; } 
    set 
    { 
    if (this.articles == value) 
     return; 
    this.articles = value; 
    RaisePropertyChanged(); 
    } 
} 

private async Task<List<Article>> GetFeedArticlesAsync(int id) 
{ 
    ... 
} 

Тогда ваша привязка данных может использовать Articles.Result, чтобы получить к полученной коллекции (которая null до GetFeedArticlesAsync завершается). Вы можете использовать NotifyTaskCompletion «из коробки» для связывания данных с ошибками (например, Articles.ErrorMessage), и он имеет несколько логических свойств удобства (IsSuccessfullyCompleted, IsFaulted) для обработки переключателей видимости.

Обратите внимание, что это будет правильно обрабатывать операции, завершающиеся не по порядку. Поскольку Articles фактически представляет собой асинхронную операцию (вместо непосредственных результатов), она немедленно обновляется при запуске новой операции. Таким образом, вы никогда не увидите устаревших результатов.

У вас нет , чтобы использовать привязку данных для вашей обработки ошибок.Вы можете сделать любую семантику, если хотите, изменив GetFeedArticlesAsync; например, для обработки исключений, передавая их в свой MessengerInstance:

private async Task<List<Article>> GetFeedArticlesAsync(int id) 
{ 
    try 
    { 
    ... 
    } 
    catch (Exception ex) 
    { 
    MessengerInstance.Send<string>("Error description", "DisplayErrorNotification"); 
    return null; 
    } 
} 

Точно так же, нет никакого понятия автоматической отмены встроенной, но опять-таки легко добавить в GetFeedArticlesAsync:

private CancellationTokenSource getFeedArticlesCts; 
private async Task<List<Article>> GetFeedArticlesAsync(int id) 
{ 
    if (getFeedArticlesCts != null) 
    getFeedArticlesCts.Cancel(); 
    using (getFeedArticlesCts = new CancellationTokenSource()) 
    { 
    ... 
    } 
} 

Это область текущего развития, поэтому, пожалуйста, сделайте улучшения или предложения API!

+0

Спасибо, Стивен, это интересный подход. Это также, по-видимому, хороший способ инициировать асинхронную загрузку данных из конструктора ViewModel. В настоящее время я использую EventToCommand для вызова метода на виртуальной машине для заполнения виртуальной машины данными (что, конечно же, мне тоже не очень нравится). Я думаю, что код чище, чем мой подход. Обязательно попробуем это. –

+0

Глядя на исходный код AsyncEx, я ожидал увидеть, что где-то предупреждение о компиляторе CS4014 игнорировалось. Немного поработав, похоже, что, по-видимому, не получается «ожидать» вызова метода «async», назначая его возвращаемое значение переменной (или, в этом случае, передавая ее функции), заставляет это предупреждение уходить. Вы знаете, что это намеренно? –

+1

Хорошо, да, это намеренно: https://msdn.microsoft.com/en-us/library/hh873131.aspx –

0
public class AsyncRunner 
{ 
    public static void Run(Task task, Action<Task> onError = null) 
    { 
     if (onError == null) 
     { 
      task.ContinueWith((task1, o) => { }, TaskContinuationOptions.OnlyOnFaulted); 
     } 
     else 
     { 
      task.ContinueWith(onError, TaskContinuationOptions.OnlyOnFaulted); 
     } 
    } 
} 

Использование в свойстве

private NavigationMenuItem _selectedMenuItem; 
public NavigationMenuItem SelectedMenuItem 
{ 
    get { return _selectedMenuItem; } 
    set 
    { 
     _selectedMenuItem = val; 
     AsyncRunner.Run(NavigateToMenuAsync(_selectedMenuItem));   
    } 
} 

private async Task NavigateToMenuAsync(NavigationMenuItem newNavigationMenu) 
{ 
    //call async tasks... 
}