Создание компонентов WPF в фоновом потоке

Я работаю над системой отчетности, серией DocumentPage должны быть созданы через DocumentPaginator, Эти документы включают в себя ряд компонентов WPF, которые должны быть созданы, чтобы в paginator содержались правильные данные при последующей отправке в XpsDocumentWriter (который в свою очередь отправляется фактическому принтеру).

Моя проблема сейчас в том, что DocumentPage Создание экземпляров занимает довольно много времени (достаточно, чтобы Windows пометила приложение как замороженное), поэтому я попытался создать их в фоновом потоке, что проблематично, поскольку WPF ожидает, что атрибуты для них будут установлены из потока GUI. Я также хотел бы, чтобы отображался индикатор выполнения, показывающий, сколько страниц было создано до сих пор. Таким образом, похоже, что я пытаюсь заставить две вещи происходить параллельно в GUI.

Проблему трудно объяснить, и я действительно не знаю, как ее решить. Короче:

  • Создать серию DocumentPagЭ.
    • К ним относятся компоненты WPF
    • Они должны быть созданы в фоновом потоке или использовать другие приемы, чтобы приложение не зависало.
  • После создания каждой страницы необходимо обновить WPF ProgressBar.

Если нет достойного способа сделать это, альтернативные решения и подходы более чем приветствуются.

5 ответов

Вы должны быть в состоянии запустить paginator в фоновом потоке, если этот поток является STA.

После того, как вы настроили свою ветку, попробуйте это до ее запуска.

thread.SetApartmentState(ApartmentState.STA);

Если вы действительно должны быть в потоке GUI, то проверьте класс Freezable, так как вам, возможно, придется переместить объекты из фонового потока в поток GUI.

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

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

        Thread t = new Thread(() =>
            {
                ProgressDialog pd = new ProgressDialog(context);
                pd.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
                pd.Show();
                System.Windows.Threading.Dispatcher.Run();
            });
        t.SetApartmentState(ApartmentState.STA);
        t.IsBackground = true;
        t.Start();

        Action();   //we need to execute the action on the main thread so that UI elements created by the action can later be displayed in the main UI

ProgressDialog был моим собственным окном WPF для отображения информации о прогрессе.

'context' содержит данные о прогрессе для моего диалога прогресса. Он включает отмененное свойство, чтобы я мог прервать действие, выполняемое в главном потоке. Он также содержит свойство complete, поэтому диалоговое окно хода выполнения может закрываться после завершения действия.

"Действие" - это метод, используемый для создания всех элементов пользовательского интерфейса. Он контролирует контекст для флага отмены и прекращает генерирование элементов пользовательского интерфейса, если флаг установлен. Он устанавливает полный флаг, когда это сделано.

Я не помню точную причину, по которой мне пришлось установить Thread 't' для потока STA и IsBackground в true, но я уверен, что без них это не сработает.

Я предполагаю, что все, что требует много времени для создания, находится внутри вашего Visual. Если это так, то существует простое решение: не создавайте реальные объекты DocumentPage и связанные с ними визуальные эффекты, пока не будет вызван DocumentPaginator.GetPage().

Пока код, который использует ваш документ, запрашивает только одну или две страницы за раз, не будет узкого места в производительности.

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

В худшем случае будет приложение, которое отображает страницы в виде миниатюр. В этом случае я бы:

  1. Представление миниатюр связывает свой ItemsSource с коллекцией "RealizedPages", которая изначально заполнена фиктивными страницами
  2. Всякий раз, когда фиктивная страница измеряется, она ставит в очередь диспетчерскую операцию в DispatcherPriority.Background для вызова DocumentPaginator.GetPage(), а затем заменяет фиктивную страницу в коллекции RealizedPages реальной страницей.

Если существуют проблемы с производительностью даже при реализации одной страницы из-за количества отдельных элементов, этот же общий подход можно использовать в любом элементе ItemsControl на странице, содержащем большое количество элементов.

Еще одно примечание: система печати XPS никогда не обрабатывает более одной DocumentPage за раз, поэтому, если вы знаете, что это ваш клиент, вы можете просто продолжать возвращать один и тот же DocumentPage снова и снова с соответствующими изменениями.

Более подробно об ответе Рэя Бернса: не могли бы вы обработать данные в классе в фоновом потоке, а затем привязать свойства DocumentPage к этому классу после завершения обработки?

Другие вопросы по тегам