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

11
我正在使用MVVM Light构建一个WP7(Windows Phone 7)应用程序。我希望由Model执行的所有任务都在后台线程上运行。然后,当工作完成时,触发一个事件以便ViewModel可以处理数据。
我已经发现我不能在WP7应用程序中异步调用委托。
目前,我正在尝试使用ThreadPool.QueueUserWorkItem()在后台线程上运行一些代码,并使用MVVM Light的DispatcherHelper.CheckBeginInvokeOnUI()在UI线程上引发事件,以向ViewModel发信号表示数据已加载(当它们尝试显示设计时视图时,这会导致VS2010和Blend 4崩溃)。
有没有示例代码可以在后台线程上运行一些代码,然后将事件分派回WP7应用程序的UI线程?
提前感谢, Jeff.
编辑 - 这是一个示例Model
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....
    }
}
3个回答

16
这是我解决问题的方法。
您的ViewModel实现了INotifyPropertyChanged,没有必要分派事件。只需在Model中“裸”引发它们,然后在ViewModel中分派RaisePropertyChanged即可。
是的,在代码中应该有某种单例模型/数据库。毕竟,如果没有一个巨大的单例模式,SQL数据库会是什么呢?由于在WP7中没有数据库,请勇敢地创建单例对象。我有一个叫做“Database”的单例对象:)
我刚刚尝试将我的数据加载线程化,并意识到最好的方法实际上是直接在模型层级别实现INotifyPropertyChanged。这并不丢人
因此,在单例Database对象中,这是我正在做的来加载和返回我的Tours“表”(请注意thread.sleep使其需要可见的时间来加载,通常为小于100ms)。Database类现在实现了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");
}

你明白吗?我正在后台线程上反序列化旅游列表,然后引发一个属性更改事件。

现在在ViewModel中,我想要一个TourViewModels列表进行绑定,一旦我看到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”进度条。很不错!


不要忘记仔细检查使用不确定进度条的性能影响:http://www.jeff.wilcox.name/2010/08/progressbarperftips2/ - Henry C
每当它们不可见时,一定要将IsDeterminte设置为False。 - Micah
请提供SafeDispatch的源代码。 - Sam
1
它非常小巧:public static void SafeDispatch( Action action ) { if ( Deployment.Current.Dispatcher.CheckAccess() ) { // 立即在当前线程上执行 action.Invoke(); } else { // 在 UI 线程上执行 Deployment.Current.Dispatcher.BeginInvoke( action ); } } - Ben Gracewood

0

我之前没有为WP7开发过,但我找到了这篇文章可能会有用

这是来自文章的餐厅哲学家示例代码,它应该能够很好地让你了解如何从另一个线程触发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);
        });
}

希望这对你有所帮助 :).

有一件事情让人困惑:你说当你使用helper来触发事件时,VS2010会崩溃... 当它崩溃时,你到底看到了什么?你是否收到了异常信息?


我在寻找你提到的源代码方面遇到了困难,你有链接吗?我很想看看Catalog.GetDinners()是如何实现的。 - Jeff R
@Jeff,这在我链接的文章中有提到(我的回答的第一句话),这是那篇文章的URL:http://chriskoenig.net/series/wp7/ - Kiril

0

Jeff,我自己还在摸索这些东西。我发布了一个类似的问题,最终通过构建一个简单的示例来回答它。这里:

一个超级简单的MVVM-Light WP7示例?

总结如下:

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

2)我使用了Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI助手,就像你一样(从我的模型中,而不是我的视图模型中)。

希望这有所帮助。


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