问题 如何在Windows Phone 7的后台线程上运行函数?


我正在使用MVVM Light来构建WP7(Windows Phone 7)应用程序。我希望模型执行的所有工作都在后台线程上运行。然后,完成工作后,引发一个事件,以便ViewModel可以处理数据。

我已经发现我无法从WP7应用程序异步调用委托。

目前我正在尝试使用ThreadPool.QueueUserWorkItem()在后台线程上运行一些代码,并使用MVVM Light的DispatcherHelper.CheckBeginInvodeOnUI()在UI线程上引发一个事件,以通知ViewModel数据已被加载(这会导致VS2010崩溃)和Blend 4,当他们试图显示设计时视图时)。

是否有任何示例代码在后台线程上运行某些代码,然后将事件调度回UI线程以获取WP7应用程序?

提前致谢, 杰夫。

编辑 - 这是一个示例模型

public class DataModel
{
    public event EventHandler<DataLoadingEventArgs> DataLoadingComplete;
    public event EventHandler<DataLoadingErrorEventArgs> DataLoadingError;
    List<Data> _dataCasch = new List<Data>();

    public void GetData()
    {
        ThreadPool.QueueUserWorkItem(func =>
        {
            try
            {
                LoadData();
                if (DataLoadingComplete != null)
                {
                    //Dispatch complete event back to the UI thread
                    DispatcherHelper.CheckBeginInvokeOnUI(() =>
                    {
                       //raise event 
                        DataLoadingComplete(this, new DataLoadingEventArgs(_dataCasch));
                    });
                }
            }
            catch (Exception ex)
            {
                if (DataLoadingError != null)
                {
                    //Dispatch error event back to the UI thread
                    DispatcherHelper.CheckBeginInvokeOnUI(() => 
                    {
                        //raise error
                        DataLoadingError(this, new DataLoadingErrorEventArgs(ex));
                    });
                }
            }
        });
    }

    private void LoadData()
    {
        //Do work to load data....
    }
}

6304
2017-07-20 18:32


起源



答案:


这就是我如何解决这个问题的方法。

您的ViewModel实现了INotifyPropertyChanged吗?没有必要发送活动。只需在模型中“裸露”它们,然后在ViewModel中调度RaisePropertyChanged。

是的,您应该在代码中使用某种单例模型/数据库。毕竟,如果不是一些巨大的单身人士,什么是SQL数据库?由于我们在WP7中没有数据库,所以不要害羞地创建单例对象。我有一个名为“数据库”:)

我刚试过在那里线程化我的dataloads,并且意识到实际上最好的方法就是在模型级别简单地实现INotifyPropertyChanged。 这并不羞耻

所以,这就是我在单例数据库对象中所做的,以加载并返回我的Tours“表”(注意thread.sleep使其花费可见的时间来加载,通常是它的子100ms)。数据库类现在实现了INotifyPropertyChanged,并在加载完成时引发事件:

public ObservableCollection<Tour> Tours
{
  get
  {
    if ( _tours == null )
    {
      _tours = new ObservableCollection<Tour>();
      ThreadPool.QueueUserWorkItem(LoadTours);
    }
    return _tours;
  }
}

private void LoadTours(object o)
{
  var start = DateTime.Now;
  //simlate lots of work 
  Thread.Sleep(5000);
  _tours = IsoStore.Deserialize<ObservableCollection<Tour>>( ToursFilename ) ??  new ObservableCollection<Tour>();
  Debug.WriteLine( "Deserialize time: " + DateTime.Now.Subtract( start ).ToString() );
  RaisePropertyChanged("Tours");
}

你跟着?我在后台线程上反序列化Tour列表,然后引发propertychanged事件。

现在在ViewModel中,我想要一个要绑定的TourViewModel列表,一旦我看到Tours表已经改变,我用linq查询选择它。在ViewModel中监听数据库事件可能有点便宜 - 将它封装在模型中可能会“更好”,但是我们不需要工作,我们不需要呃?

在Viewmodel的构造函数中钩住Database事件:

public TourViewModel()
{
Database.Instance.PropertyChanged += DatabasePropertyChanged;
}

听取适当的表变化(我们喜欢魔术弦!;-)):

private void DatabasePropertyChanged(object sender, PropertyChangedEventArgs e)
{
  if(e.PropertyName == "Tours")
  {
    LoadTourList();
  }
}

从表中选择我想要的记录,然后告诉视图有新数据:

public void LoadTourList()
{
  AllTours = ( from t in Database.Instance.Tours
    select new TourViewModel( t ) ).ToList();

  RaisePropertyChanged( "AllTours" );
}

最后,在您的ViewModelBase中,最好检查您的RaisePropertyChanged是否需要调度。我的“SafeDispatch”方法与MVVMlight中的方法几乎相同:

private void RaisePropertyChanged(string property)
{
  if ( PropertyChanged != null )
  {
    UiHelper.SafeDispatch(() =>
      PropertyChanged(this, new PropertyChangedEventArgs(property)));
  }
}

这在我的代码中完美运行,我认为相当整洁?

最后,对于专家来说是额外的:在WP7中,向您的页面添加带有IsIndeterminate = True的ProgressBar可能会很好 - 这将显示“虚线”进度条。然后你可以做的是当ViewModel首次加载时你可以将“ProgressBarVisible”属性设置为Visible(并引发相关的PropertyChanged事件)。将ProgressBar的可见性绑定到此ViewModel属性。触发Database PropertyChanged事件时,将可见性设置为Collapsed以使进度条消失。

这样,在反序列化运行时,用户将在其屏幕顶部看到“IsIndeterminate”进度条。太好了!


16
2017-07-21 20:52



不要忘记只是仔细检查使用不确定进度条的性能影响: jeff.wilcox.name/2010/08/progressbarperftips2 - Henry C
只要它们不可见,肯定会设置IsDeterminte = False。 - Micah
SafeDispatch的来源会很好。 - Sam
它非常小:public static void SafeDispatch(Action action){if(Deployment.Current.Dispatcher.CheckAccess()){//现在在这个线程上执行action.Invoke(); } else {//在UI线程上执行它Deployment.Current.Dispatcher.BeginInvoke(action); }} - Ben Gracewood


答案:


这就是我如何解决这个问题的方法。

您的ViewModel实现了INotifyPropertyChanged吗?没有必要发送活动。只需在模型中“裸露”它们,然后在ViewModel中调度RaisePropertyChanged。

是的,您应该在代码中使用某种单例模型/数据库。毕竟,如果不是一些巨大的单身人士,什么是SQL数据库?由于我们在WP7中没有数据库,所以不要害羞地创建单例对象。我有一个名为“数据库”:)

我刚试过在那里线程化我的dataloads,并且意识到实际上最好的方法就是在模型级别简单地实现INotifyPropertyChanged。 这并不羞耻

所以,这就是我在单例数据库对象中所做的,以加载并返回我的Tours“表”(注意thread.sleep使其花费可见的时间来加载,通常是它的子100ms)。数据库类现在实现了INotifyPropertyChanged,并在加载完成时引发事件:

public ObservableCollection<Tour> Tours
{
  get
  {
    if ( _tours == null )
    {
      _tours = new ObservableCollection<Tour>();
      ThreadPool.QueueUserWorkItem(LoadTours);
    }
    return _tours;
  }
}

private void LoadTours(object o)
{
  var start = DateTime.Now;
  //simlate lots of work 
  Thread.Sleep(5000);
  _tours = IsoStore.Deserialize<ObservableCollection<Tour>>( ToursFilename ) ??  new ObservableCollection<Tour>();
  Debug.WriteLine( "Deserialize time: " + DateTime.Now.Subtract( start ).ToString() );
  RaisePropertyChanged("Tours");
}

你跟着?我在后台线程上反序列化Tour列表,然后引发propertychanged事件。

现在在ViewModel中,我想要一个要绑定的TourViewModel列表,一旦我看到Tours表已经改变,我用linq查询选择它。在ViewModel中监听数据库事件可能有点便宜 - 将它封装在模型中可能会“更好”,但是我们不需要工作,我们不需要呃?

在Viewmodel的构造函数中钩住Database事件:

public TourViewModel()
{
Database.Instance.PropertyChanged += DatabasePropertyChanged;
}

听取适当的表变化(我们喜欢魔术弦!;-)):

private void DatabasePropertyChanged(object sender, PropertyChangedEventArgs e)
{
  if(e.PropertyName == "Tours")
  {
    LoadTourList();
  }
}

从表中选择我想要的记录,然后告诉视图有新数据:

public void LoadTourList()
{
  AllTours = ( from t in Database.Instance.Tours
    select new TourViewModel( t ) ).ToList();

  RaisePropertyChanged( "AllTours" );
}

最后,在您的ViewModelBase中,最好检查您的RaisePropertyChanged是否需要调度。我的“SafeDispatch”方法与MVVMlight中的方法几乎相同:

private void RaisePropertyChanged(string property)
{
  if ( PropertyChanged != null )
  {
    UiHelper.SafeDispatch(() =>
      PropertyChanged(this, new PropertyChangedEventArgs(property)));
  }
}

这在我的代码中完美运行,我认为相当整洁?

最后,对于专家来说是额外的:在WP7中,向您的页面添加带有IsIndeterminate = True的ProgressBar可能会很好 - 这将显示“虚线”进度条。然后你可以做的是当ViewModel首次加载时你可以将“ProgressBarVisible”属性设置为Visible(并引发相关的PropertyChanged事件)。将ProgressBar的可见性绑定到此ViewModel属性。触发Database PropertyChanged事件时,将可见性设置为Collapsed以使进度条消失。

这样,在反序列化运行时,用户将在其屏幕顶部看到“IsIndeterminate”进度条。太好了!


16
2017-07-21 20:52



不要忘记只是仔细检查使用不确定进度条的性能影响: jeff.wilcox.name/2010/08/progressbarperftips2 - Henry C
只要它们不可见,肯定会设置IsDeterminte = False。 - Micah
SafeDispatch的来源会很好。 - Sam
它非常小:public static void SafeDispatch(Action action){if(Deployment.Current.Dispatcher.CheckAccess()){//现在在这个线程上执行action.Invoke(); } else {//在UI线程上执行它Deployment.Current.Dispatcher.BeginInvoke(action); }} - Ben Gracewood


我以前没有为WP7开发,但我发现了 这篇文章可能有用

这篇文章中的Dining Philosopher示例代码应该可以让您对如何从另一个线程向UI引发事件有所了解:

public DinnersViewModel(IDinnerCatalog catalog)
{
    theCatalog = catalog;
    theCatalog.DinnerLoadingComplete +=
        new EventHandler<DinnerLoadingEventArgs>(
              Dinners_DinnerLoadingComplete);
}

public void LoadDinners()
{
    theCatalog.GetDinners();
}

void Dinners_DinnerLoadingComplete(
    object sender, DinnerLoadingEventArgs e)
{
    // Fire Event on UI Thread
    View.Dispatcher.BeginInvoke(() =>
        {
            // Clear the list
            theDinners.Clear();

            // Add the new Dinners
            foreach (Dinner d in e.Results)
                theDinners.Add(d);

            if (LoadComplete != null)
                LoadComplete(this, null);
        });
}

我希望它有用:)。

有一件事令人困惑:你说当你使用帮助器来引发事件时,VS2010会崩溃......当它崩溃时你究竟看到了什么?你有例外吗?


0
2017-07-20 19:41



我无法找到您引用的源代码,您是否有链接?我很高兴看到如何实现theCatalog.GetDinners()。 - Jeff R
@Jeff,这是我链接的文章(我的回答的第一句),这是该文章的URL: chriskoenig.net/series/wp7 - Kiril


杰夫,我还在自己搞清楚这些东西。我发布了一个类似的问题,最后通过构建一个简单的样本来回答它。这里:

一个超级简单的MVVM-Light WP7样本?

摘要是:

1)我从中派生了我的模型(是我的模型) ViewModelBase。这给了我Mvvm-Light的消息传递和实现 INotifyPropertyChanged 这很方便。你可以说这不是“纯粹的”,但我认为这不重要。

2)我使用了Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI就像你做的那样帮助(从我的模型,而不是我的ViewModel)。

希望这可以帮助。


0
2017-09-08 20:52