Как запустить функцию в фоновом потоке для Windows Phone 7?

Я использую MVVM Light для создания приложения WP7 (Windows Phone 7). Я хочу, чтобы вся работа, выполняемая моделью, выполнялась в фоновом потоке. Затем, когда работа выполнена, поднимите событие, чтобы ViewModel мог обрабатывать данные.

Я уже выяснил, что я не могу асинхронно вызывать делегат из приложения WP7.

В настоящее время я пытаюсь использовать ThreadPool.QueueUserWorkItem() для запуска некоторого кода в фоновом потоке и использовать MVVM Light DispatcherHelper.CheckBeginInvodeOnUI(), чтобы поднять событие в потоке пользовательского интерфейса, чтобы сигнализировать ViewModel о том, что данные были загружены ( это приводит к сбоям VS2010 и Blend 4, когда они пытаются отобразить представление времени разработки).

Есть ли какой-нибудь пример кода для запуска некоторого кода в фоновом потоке, а затем отправить событие обратно в поток пользовательского интерфейса для 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....
    }
}

Ответ 1

Вот как я подхожу к решению этого.

В вашей модели ViewModel реализовано право INotifyPropertyChanged? Там нет необходимости отправлять события. Просто поднимите их "голыми" в модели, а затем отправьте RaisePropertyChanged в ViewModel.

И да, в вашем коде должна быть какая-то модель/база данных singleton. В конце концов, что такое база данных SQL, если не какой-то гигантский синглтон? Поскольку у нас нет базы данных в WP7, не стесняйтесь создавать одноэлементный объект. У меня один называется "База данных":)

Я только что попробовал потопить мои dataloads там и понял, что на самом деле лучший подход - это просто реализовать INotifyPropertyChanged прямо на уровне модели. Нет никакого позора в этом.

Поэтому, учитывая то, что я делаю в объекте Singleton Database, загружать и возвращать таблицу "Путевые расходы" (обратите внимание на thread.sleep, чтобы заставить загружать видимое количество времени, обычно его под 100 мс), Класс базы данных теперь реализует 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 в фоновом потоке, а затем создаю событие, измененное собственностью.

Теперь в ViewModel мне нужен список TourViewModels для привязки, который я выбираю с помощью запроса linq, как только вижу, что таблица Tours изменилась. Вероятно, немного дешево слушать событие Database в ViewModel - возможно, было бы "лучше" инкапсулировать это в модели, но пусть не делает работу, мы не нуждаемся в ней?

Захватите событие Database в конструкторе Viewmodel:

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 может быть полезно добавить ProgressBar с IsIndeterminate = True на вашу страницу - это отобразит индикатор "пунктирный". Тогда, когда вы впервые загрузите ViewModel, вы можете установить свойство "ProgressBarVisible" для Visible (и повысить связанное событие PropertyChanged). Привяжите видимость ProgressBar к этому свойству ViewModel. Когда событие Database PropertyChanged срабатывает, установите видимость на "Свернутый", чтобы ускорить работу.

Таким образом, пользователь будет видеть индикатор выполнения "IsIndeterminate" в верхней части экрана во время выполнения десериализации. Ницца!

Ответ 2

Я раньше не разрабатывал WP7, но нашел эту статью, которая может быть полезна!

Вот пример кода Dining Philosopher из статьи, который должен дать вам хорошую идею о том, как поднять событие в пользовательский интерфейс из другого потока:

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 падает... что именно вы видите, когда он падает? Вы получаете исключение?

Ответ 3

Джефф, я все еще разбираюсь в этом. Я опубликовал аналогичный вопрос и в итоге сам ответил на него, построив простой образец. Здесь:

Простой образец MVVM-Light WP7?

Резюме:

1) Я получил мою модель (да моя модель) от ViewModelBase. Это дает мне Mvvm-Light реализацию обмена сообщениями и INotifyPropertyChanged, что удобно. Вы можете утверждать, что это не "чисто", но я не думаю, что это имеет значение.

2) Я использовал помощник Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI так же, как и вы (из моей модели, а не моей модели ViewModel).

Надеюсь, это поможет.