Многопоточность WPF: используется Dispatcher, но пользовательский интерфейс все еще зависает?

У меня есть небольшая проблема с зависанием моего пользовательского интерфейса, хотя я использую Dispatcher, и, прежде чем я продолжу, мне интересно, так ли я обрабатываю поиск данных.

Прямо сейчас у меня есть главное окно, создающее View и ViewModel. Затем внутри нового потока (используя Dispatcher) он устанавливает View.DataContext = ViewModel. Очень большая коллекция ObservableCollection создается лениво, когда срабатывает привязка, вызывающая замедление. Однако кажется, что некоторые другие элементы пользовательского интерфейса, которые должны отображаться до этого замедления, на самом деле не отображаются.

   private void ButtonClick(Object sender, RoutedEventArgs e)
   {
        MyView view = new MyView();
        MyViewModel vm = new MyViewModel();

        TabItem tabItem = new TabItem();
        tabItem.Header = "MyView";
        tabItem.Content = view;

        MyTabCollection.Items.Add(tabItem);

        Window working = new Working();
        working.Show();

        ThreadStart thread = delegate()
        {
            DispatcherOperation operation = Dispatcher.BeginInvoke(
                DispatcherPriority.Normal,
                new Action(delegate()
                {
                    view.DataContext = vm;
                    ((FrameworkElement)view.Parent).Focus();
                    working.Close();
                }
                )
            );
        };

        Thread theThread = new Thread(thread);
        theThread.Start();
    }

Это в основном говорит о том, что предполагается создать представление и модель представления, а затем добавить представление в коллекцию вкладок, которая у меня есть (что означает, что она должна отображать как минимум новую вкладку). Кроме того, должно появиться окно «Работает...». После этого отдельный поток должен связать ViewModel с представлением, сфокусироваться на этой вкладке и закрыть рабочее окно. Проблема в том, что первая часть не отображается, пока все не будет сделано; Вкладка не отображается, и рабочее окно не отображается до тех пор, пока новый поток фактически не завершится (что приводит к немедленному отображению/закрытию рабочего окна). Я предполагаю, что это может быть связано с тем, как я извлекаю данные, но я не уверен. Вот как это делается:

  1. Создать представление
  2. Создать ViewModel
  3. Создайте TabItem с Content, установленным в View, и добавьте TabItem в TabCollection.
  4. Создать/Показать окно "Работа..."
  5. Диспетчер: Установите View.DataContext = ViewModel. Это событие запускает DataBindings, которые, в свою очередь, захватывают ObservableCollection. Поскольку OC создается лениво, сейчас он создается (это узкое место). ‹-- Это портит мой отдельный поток/диспетчер?
  6. Диспетчер: установите фокус на вкладку
  7. Закройте окно "Работает..."

person myermian    schedule 10.11.2010    source источник


Ответы (3)


Все, что делает ваш дополнительный поток, — это маршаллинг другого обратного вызова в поток диспетчера. Предположительно, вы действительно хотите поработать над дополнительным потоком, или нет смысла его создавать.

В идеале ваш дополнительный поток должен правильно извлекать все данные, оставляя вас только для того, чтобы фактически подключить все это в потоке диспетчера. Важно решить, какую работу вам нужно выполнить в потоке пользовательского интерфейса, а какую — в фоновом потоке.

person Jon Skeet    schedule 10.11.2010
comment
Я вроде как понимаю, о чем вы говорите... но если мои классы разделены следующим образом: View, ViewModel, Model и DAL (DataAccessLayer), где мне добавить этот отдельный поток, чтобы он действительно работал? - person myermian; 10.11.2010
comment
@myermian: Это действительно зависит от того, как работает ваш код. Вы можете вызвать свой DAL или модель с помощью асинхронного вызова, чтобы получить данные, а затем вернуться к потоку пользовательского интерфейса, когда все это будет там. - person Jon Skeet; 11.11.2010

Очевидно, ваш анализ проблемы верен. Ваша модель представления лениво загружает данные, когда это необходимо, и этого не происходит до обратного вызова Dispatcher, после чего вы снова возвращаетесь в поток пользовательского интерфейса, и все блокируется.

На мой взгляд, решение состоит в том, чтобы выполнять потоки на уровне доступа к данным:

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

Для итогов и тому подобного: та же идея. По мере поступления данных общее количество увеличивается, и происходят события (автоматически для DependencyProperty или с помощью INotifyPropertyChanged).

Кроме того, уровень данных должен иметь свойство parallel для каждой коллекции, суммы или другого загружаемого с задержкой значения, указывающее, полностью ли оно загружено или нет, позволяя пользовательскому интерфейсу выделять не полностью загруженные разделы. Также удобно иметь где-то общий флаг «загрузки», который можно использовать для выделения серым цветом разделов пользовательского интерфейса, когда что-либо вообще загружается (таким образом проще написать пользовательский интерфейс).

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

person Ray Burns    schedule 10.11.2010
comment
+1, я использовал шаблон сбора «незавершенное производство», и он прекрасно работает. Вы видите преимущество этого подхода в VS 2010 в диалоговом окне «Добавить ссылки» (в VS 2008 загрузка списка доступных ссылок в GAC заняла бы целую вечность), а также в окне «Удаление программы» Windows 7. Одна вещь, которую следует иметь в виду при использовании этого метода, заключается в том, что фактические обратные вызовы INotifyCollectionChanged должны быть отправлены в поток пользовательского интерфейса, и вы должны соблюдать осторожность при изменении коллекции. Я обнаружил, что полезно иметь коллекцию «буферов», используемую исключительно для «переноса» элементов в поток пользовательского интерфейса. - person Dan Bryant; 11.11.2010

Ваш DispatcherPriority установлен на Normal — попробуйте установить его на Background, так как это может улучшить визуализацию.

person Dean Chalk    schedule 18.11.2010