XamlReader.Load в фоновом потоке. Является ли это возможным? - PullRequest
8 голосов
/ 22 марта 2011

Приложение WPF имеет операцию загрузки пользовательского элемента управления из отдельного файла, используя метод XamlReader.Load():

StreamReader mysr = new StreamReader(pathToFile);
DependencyObject rootObject = XamlReader.Load(mysr.BaseStream) as DependencyObject;
ContentControl displayPage = FindName("displayContentControl") as ContentControl;
displayPage.Content = rootObject;

Процесс занимает некоторое время из-за размера файла, поэтому пользовательский интерфейс останавливается на несколько секунд.

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

При попытке использовать BackgroundWorker я получил ошибку: Вызывающий поток должен быть STA, поскольку для многих компонентов пользовательского интерфейса требуется это

Итак, я пошел другим путем:

 private Thread _backgroundThread;
 _backgroundThread = new Thread(DoReadFile);
 _backgroundThread.SetApartmentState(ApartmentState.STA);
 _backgroundThread.Start();
 void DoReadFile()
 {
   StreamReader mysr3 = new StreamReader(path2);
   Dispatcher.BeginInvoke(
           DispatcherPriority.Normal,
           (Action<StreamReader>)FinishedReading,
           mysr3);
 }

 void FinishedReading(StreamReader stream)
    {            
        DependencyObject rootObject = XamlReader.Load(stream.BaseStream) as DependencyObject;
        ContentControl displayPage = FindName("displayContentControl") as ContentControl;
        displayPage.Content = rootObject;
    }

Это ничего не решает, потому что все трудоемкие операции остаются в потоке пользовательского интерфейса.

Когда я пытаюсь так сделать, все разбора в фоновом режиме:

private Thread _backgroundThread;
_backgroundThread = new Thread(DoReadFile);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
 void DoReadFile()
 {
  StreamReader mysr3 = new StreamReader(path2);      
  DependencyObject rootObject3 = XamlReader.Load(mysr3.BaseStream) as DependencyObject;
        Dispatcher.BeginInvoke(
           DispatcherPriority.Normal,
           (Action<DependencyObject>)FinishedReading,
           rootObject3);
  }

  void FinishedReading(DependencyObject rootObject)
  {            
    ContentControl displayPage = FindName("displayContentControl") as ContentControl;
    displayPage.Content = rootObject;
  } 

Я получил исключение: Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку. (в загруженном UserControl присутствуют другие элементы управления, которые могут выдавать ошибку)

Есть ли способ выполнить эту операцию таким образом, чтобы интерфейс реагировал?

Ответы [ 5 ]

7 голосов
/ 22 марта 2011

Получение XAML для загрузки фонового потока по сути не является началом.Компоненты WPF имеют сходство с потоками и, как правило, используются только из потоков, в которых они созданы.Таким образом, загрузка в фоновом потоке сделает пользовательский интерфейс отзывчивым, но создаст компоненты, которые затем не могут быть подключены к потоку пользовательского интерфейса.

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

2 голосов
/ 22 марта 2011

Как вы выяснили, вы не можете использовать XamlReader.Load, если поток не является STA, и даже если это так, вам нужно будет запустить насос сообщений и направить весь доступ к элементам управления, созданным через него. Это фундаментальный способ работы WPF, и вы не можете пойти против этого.

Итак, ваши единственные реальные варианты:

  1. Разбейте XAML на более мелкие части.
  2. Начать новый поток STA для каждого Load вызова. После возврата Load поток должен будет запустить цикл обработки сообщений и управлять созданными им элементами управления. Ваше приложение должно учитывать тот факт, что разные элементы управления теперь принадлежат разным потокам.
1 голос
/ 22 марта 2011

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

http://www.codehosting.net/blog/BlogEngine/post/Opening-WPF-Windows-on-a-new-thread.aspx

http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/

0 голосов
/ 29 апреля 2017

System.Xaml имеет класс Xaml​Background​Reader, возможно, вы можете заставить его работать на себя.Разбор XAML в фоновом потоке, но создание объектов в потоке пользовательского интерфейса.

0 голосов
/ 01 августа 2011

Вы можете вызвать метод, который позволяет передать управление другому потоку:

http://msdn.microsoft.com/en-us/library/ms748331.aspx

Это называется .Freeze ()

...