如何实现异步的INotifyPropertyChanged

4
我有一个类,其中的属性与我的视图绑定。为了保持视图的最新状态,我实现了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"相同的线程中创建)。
据我所知,通常需要创建一个新的线程并运行它(同时等待它)。 ´await Task.Run(...);´ 应该可以完成这个工作。
由于PropertyChanged事件直接影响UI,因此在UI线程中调用它似乎是一个好决定。这就是为什么我调用了Dispatcher.BeginInvoke。
我不明白的是:上述异常是由于不同的线程负责数据导致的。但是我明确地在我的UI线程上调用了事件,对象也应该由UI线程创建。那么为什么会出现异常呢?
我的主要问题是:如何通常实现INotifyPropertyChanged接口的事件以避免或处理大多数异步编程问题,例如上面的问题?构建函数时应考虑什么?

你正在线程上引发propertychanged事件,因此如果它从未创建过UI元素,则无法访问它们。 - CodingYoshi
阅读这篇文章,真正理解哪些代码是由UI线程执行的,哪些是由其他线程执行的。 - CodingYoshi
该属性本身是否像位图一样是DependencyObject? - Mike Zboray
1个回答

10

现在我有一些耗费性能的函数,它们会让我的应用程序卡顿。

如果您真的在进行异步“网络操作”,那么它不应该使应用程序卡顿。

我的主要问题是:如何通常实现INotifyPropertyChanged接口的事件以避免或处理上述大多数异步编程问题?

我更喜欢的方法是不要在事件引发代码中处理此问题。相反,将其余代码结构化,以使其尊重UI层。

换句话说,将您的“服务”(或“业务逻辑”)代码与您的“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的调用,虽然它应该是多余的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接