简体   繁体   English

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

[英]How to run a function on a background thread for Windows Phone 7?

I'm using MVVM Light to build a WP7 (Windows Phone 7) application. 我正在使用MVVM Light来构建WP7(Windows Phone 7)应用程序。 I wish to have all the work performed by the Model to be run on a background thread. 我希望模型执行的所有工作都在后台线程上运行。 Then, when the work is done, raise an event so that the ViewModel can process the data. 然后,完成工作后,引发一个事件,以便ViewModel可以处理数据。

I have already found out that I cannot invoke a Delegate asynchronously from an WP7 app. 我已经发现我无法从WP7应用程序异步调用委托。

Currently I am trying to use ThreadPool.QueueUserWorkItem() to run some code on a background thread and use MVVM Light's DispatcherHelper.CheckBeginInvodeOnUI() to raise an event on the UI thread to signal the ViewModel that the data has been loaded (this crashes VS2010 and Blend 4 when they try to display a design-time view). 目前我正在尝试使用ThreadPool.QueueUserWorkItem()在后台线程上运行一些代码,并使用MVVM Light的DispatcherHelper.CheckBeginInvodeOnUI()在UI线程上引发一个事件,以通知ViewModel数据已被加载(这会导致VS2010崩溃)和Blend 4在尝试显示设计时视图时)。

Is there any sample code to run some code on a background thread and then dispatch an event back to the UI thread for a WP7 app? 是否有任何示例代码在后台线程上运行某些代码,然后将事件调度回UI线程以获取WP7应用程序?

Thanks in advance, Jeff. 杰夫,先谢谢你。

Edit - Here is a sample 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....
    }
}

Here's how I'd approach a solution to this. 以下是我如何解决这个问题的方法。

Your ViewModel implements INotifyPropertyChanged right? 您的ViewModel实现了INotifyPropertyChanged吗? There's no need to dispatch the Events. 没有必要发送活动。 Just raise them "bare" in the Model, then dispatch the RaisePropertyChanged in the ViewModel. 只需在模型中“裸露”它们,然后在ViewModel中调度RaisePropertyChanged。

And yes, you should have some sort of singleton model/database in your code. 是的,你的代码中应该有一些单例模型/数据库。 After all, what is a SQL Database if not some gigantic singleton? 毕竟,如果不是一些巨大的单身人士,什么是SQL数据库? Since we don't have a database in WP7, don't be shy creating a singleton object. 由于我们在WP7中没有数据库,所以不要害羞地创建单例对象。 I have one called "Database" :) 我有一个名为“数据库”:)

I've just tried threading my dataloads in there, and realise that in fact the best approach is simply implementing INotifyPropertyChanged right down at the model level. 我刚试过在那里线程化我的dataloads,并意识到实际上最好的方法就是在模型级别直接实现INotifyPropertyChanged。 There's no shame in this . 这没有什么可耻的

So given that, here's what I'm doing in the singleton Database object to load and return my Tours "table" (note the thread.sleep to make it take a visible amount of time to load, normally its sub 100ms). 所以,这就是我在单例数据库对象中所做的事情,以加载并返回我的Tours“表”(注意thread.sleep使得它需要花费可见的时间来加载,通常是它的子100ms)。 Database class now implements INotifyPropertyChanged, and raises events when loading is completed: 数据库类现在实现了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");
}

You follow? 你跟着? I'm deserializing the Tour list on a background thread, then raising a propertychanged event. 我在后台线程中反序列化Tour列表,然后引发propertychanged事件。

Now in the ViewModel, I want a list of TourViewModels to bind to, which I select with a linq query once I see that the Tours table has changed. 现在在ViewModel中,我想要一个要绑定的TourViewModel列表,一旦我看到Tours表已经改变,我就用linq查询选择它。 It's probably a bit cheap to listen for the Database event in the ViewModel - it might be "nicer" to encapsulate that in the model, but let's not make work we we don't need to eh? 在ViewModel中监听数据库事件可能有点便宜 - 将它封装在模型中可能“更好”,但是我们不需要做工作吗?

Hook the Database event in the Viewmodel's constructor: 在Viewmodel的构造函数中钩住Database事件:

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

Listen for the appropriate table change (we love magic strings! ;-) ): 听取适当的表格变化(我们喜欢魔术弦!;-)):

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

Select the records I want from the table, then tell the view there is new data: 从表中选择我想要的记录,然后告诉视图有新数据:

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

  RaisePropertyChanged( "AllTours" );
}

And lastly, in your ViewModelBase, it's best to check if your RaisePropertyChanged needs dispatching. 最后,在您的ViewModelBase中,最好检查您的RaisePropertyChanged是否需要调度。 My "SafeDispatch" method is pretty much the same as the one from MVVMlight: 我的“SafeDispatch”方法与MVVMlight中的方法几乎相同:

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

This works perfectly in my code, and I think is fairly tidy? 这在我的代码中完美运行,我认为相当整洁?

Lastly, extra for experts: in WP7, it might be good to add a ProgressBar with IsIndeterminate=True to your page - this will display the "dotted" progress bar. 最后,对于专家来说是额外的:在WP7中,向您的页面添加带有IsIndeterminate = True的ProgressBar可能会很好 - 这将显示“虚线”进度条。 Then what you can do is when the ViewModel first loads you could set a "ProgressBarVisible" property to Visible (and raise the associated PropertyChanged event). 然后你可以做的是当ViewModel首次加载时你可以将“ProgressBarVisible”属性设置为Visible(并引发相关的PropertyChanged事件)。 Bind the ProgressBar's visibility to this ViewModel property. 将ProgressBar的可见性绑定到此ViewModel属性。 When the Database PropertyChanged event fires, set the visibility to Collapsed to make the progressbar go away. 触发Database PropertyChanged事件时,将可见性设置为Collapsed以使进度条消失。

This way the user will see the "IsIndeterminate" progress bar at the top of their screen while the deserialization is running. 这样,在反序列化运行时,用户将在其屏幕顶部看到“IsIndeterminate”进度条。 Nice! 太好了!

I have not developed for WP7 before, but I found this article that might be useful ! 我以前没有为WP7开发,但我发现这篇文章可能有用

Here is the Dining Philosopher sample code from the article that should give you a good idea on how to raise an event to the UI from another thread: 这篇文章中的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);
        });
}

I hope it's helpful :). 我希望它有用:)。

One thing that's confusing: you said that when you use the helper to raise the event, then VS2010 crashes... what exactly are you seeing when it's crashing? 有一点令人困惑:你说当你使用帮助器来引发事件时,VS2010会崩溃......当你崩溃时你究竟看到了什么? Are you getting an exception? 你有例外吗?

Jeff, I'm still figuring this stuff out myself. 杰夫,我还在自己搞这个东西。 I posted a similar question and ended up answering it myself by building a simple sample. 我发布了一个类似的问题,最后通过构建一个简单的样本来回答它。 Here: 这里:

A super-simple MVVM-Light WP7 sample? 一个超级简单的MVVM-Light WP7样本?

The summary is: 摘要是:

1) I derived my Model (yes my model) from ViewModelBase . 1)我从ViewModelBase派生了我的模型(是的我的模型)。 This gives me Mvvm-Light's implementation of messaging and INotifyPropertyChanged which is handy. 这给了我Mvvm-Light的消息传递和INotifyPropertyChanged的实现,这很方便。 You could argue this is not "pure" but I don't think it matters. 你可以说这不是“纯粹的”,但我认为这不重要。

2) I used Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI helper just as you did (from my Model, NOT my ViewModel). 2)我使用Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI帮助器(就像我的模型,而不是我的ViewModel)。

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM