2016-12-29 3 views
1

У меня есть класс со свойствами, привязанными к моему представлению. Чтобы обновить мое представление, я реализую INotifyPropertyChanged и каждый раз подготавливаю событие при изменении свойств.Как реализовать async INotifyPropertyChanged

Теперь у меня есть некоторые тяжелые функции, которые замораживают мое приложение. Я хочу поставить их в фоновое задание.

Во-первых: здесь мой текущий подход

(например, по нажатию кнопки)

private async void HeavyFunc() 
{ 
    foreach (var stuff) 
    { 
     count += await Task.Run(() => stuff.Fetch()); 
    } 

    if (count == 0) 
     //... 
} 

материал класса

public async Task<int> Fetch() 
{ 
    //network stuff 

    RaisePropertyChanged("MyProperty"); 
} 

public async void RaisePropertyChanged(string pChangedProperty) 
{ 
    await Application.Current.Dispatcher.BeginInvoke(
     System.Windows.Threading.DispatcherPriority.Normal, 
     new ThreadStart(() => 
     { 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pChangedProperty); 
     } 
    ); 
} 

Код выше дает исключение ("DependencySource" должен быть создан тот же поток, что и «DependencyObject»).

AFAIK, вам обычно нужно создать новый поток и запустить его (ожидая его). «Задача». (...); »должна выполнять эту работу.

Поскольку событие PropertyChanged непосредственно влияет на пользовательский интерфейс, вызов его в потоке пользовательского интерфейса, по-видимому, является хорошим решением. Вот почему я вызываю Dispatcher.BeginInvoke.

Что я не понимаю: исключение, указанное выше, возникает, когда разные данные отвечают за данные. Но я явно вызываю событие на свой UI-поток, и объект должен быть создан также через UI-поток. Так почему я получаю исключение?

Мой главный вопрос: Как реализовать события для интерфейса INotifyPropertyChanged, как правило, избегать или обрабатывать большинство проблем программирования async, как описано выше? Что следует учитывать при построении функций?

+0

Вы поднимаете событие propertychanged в потоке, чтобы он не смог получить доступ к элементам ui, если он их никогда не создавал. – CodingYoshi

+0

Прочитайте [this] (http://stackoverflow.com/questions/41271185/interface-freeze-when-trying-to-update-datagridview/41272238#41272238), чтобы действительно понять, какой код выполняется потоком ui vs another thread – CodingYoshi

+0

Является ли само свойство объектом DepedencyObject как растровое изображение? –

ответ

4

Теперь у меня есть некоторые тяжелые функции, которые заморожают мое приложение.

Если вы действительно занимаетесь асинхронным «сетевым материалом», то это не должно замораживать приложение.

Мой главный вопрос: Как реализовать события для интерфейса INotifyPropertyChanged, как правило, избегать или обрабатывать большинство проблем программирования async, как описано выше?

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

Другими словами, разделить (или «бизнес-логики») код «службы» из вашего «UI» код так, что он работает так:

// In StuffService class: 
public async Task<Result> FetchAsync() 
{ 
    //network stuff 
    return result; 
} 

// In StuffViewModel class: 
public async void ButtonClicked() 
{ 
    foreach (var stuff) 
    { 
    var result = await Task.Run(() => _stuffService.FetchAsync()); 
    MyProperty = result.MyProperty; 
    count += result.Count; 
    } 

    if (count == 0) 
    //... 
} 

public Property MyProperty 
{ 
    get { return _myProperty; } 
    set 
    { 
    _myProperty = value; 
    RaisePropertyChanged(); 
    } 
} 
private void RaisePropertyChanged([CallerMemberName] string pChangedProperty = null) 
{ 
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pChangedProperty)); 
} 

Таким образом, нет никакой ручной резьбы, прыжки, все свойства имеют стандартную реализацию ViewModel, код проще и легче поддерживать и т.д.

я оставил в вызове Task.Run, хотя это должно быть излишним, если ваши сетевые вызовы действительно асинхронно.