简体   繁体   English

在后台线程中创建WPF组件

[英]Creating WPF components in background thread

Im working on a reporting system, a series of DocumentPage are to be created through a DocumentPaginator . 进出口报告系统的工作,一系列DocumentPage是通过创建DocumentPaginator These documents include a number of WPF components that are to be instantiated so the paginator includes the correct things when later sent to the XpsDocumentWriter (which in turn is sent to the actual printer). 这些文档包括许多要实例化的WPF组件 ,因此分页器在以后发送到XpsDocumentWriter (然后又发送到实际打印机)时包括正确的内容。

My problem now is that the DocumentPage instances take quite a while to create (enough for Windows to mark the application as frozen) so I tried to create them in a background thread , which is problematic since WPF expects the attributes on them to be set from the GUI thread. 我现在的问题是,创建DocumentPage实例要花很长时间(Windows足以将应用程序标记为冻结),所以我尝试在后台线程中创建它们,这是有问题的,因为WPF希望从中设置它们的属性。 GUI线程。 I would also like to have a progress bar showing up, indicating how many pages have been created so far. 我还希望显示一个进度条,指示到目前为止已创建了多少个页面。 Thus, it looks like Im trying to get two things to happen in parallell on the GUI. 因此,似乎Im试图在GUI上并行发生两件事。

The problem is hard to explain and Im really not sure how to tackle it. 这个问题很难解释,我真的不确定如何解决。 In short: 简而言之:

  • Create a series of DocumentPag e's. 创建一系列DocumentPag e。
    • These include WPF components 这些包括WPF组件
    • These are to be created on a background thread, or use some other trick so the application isnt frozen. 这些将在后台线程上创建,或使用其他技巧使应用程序不冻结。
  • After each page is created, a WPF ProgressBar should be updated. 创建每个页面后,应更新WPF ProgressBar

If there is no decent way to do this, alternate solutions and approaches are more than welcome. 如果没有做到这一点的体面方法,则非常欢迎使用替代解决方案和方法。

You should be able to run the paginator in a background thread as long as the thread is STA. 只要线程是STA,您就应该能够在后台线程中运行分页器。

After you've set up your thread, try this prior to running it. 设置线程后,请在运行线程之前尝试此操作。

thread.SetApartmentState(ApartmentState.STA);

If you really must be on the GUI thread, then check out the Freezable class, as you might have to move the objects from your background thread to the GUI thread. 如果确实必须使用GUI线程,请检出Freezable类,因为您可能必须将对象从后台线程移至GUI线程。

A little late to the game on this one, but I just worked out a solution to this so I thought I would share. 在这个游戏上,游戏有点晚了,但是我只是为此解决了一个办法,所以我想分享一下。 In order to display the UI elements they have to be created on the UI thread on which they will be displayed. 为了显示UI元素,必须在将显示它们的UI线程上创建它们。 Since the long running task is on the UI thread, it will prevent a progress bar from updating. 由于长时间运行的任务在UI线程上,因此它将阻止进度条的更新。 To get around this, I created the progress bar on a new UI thread and created the pages on the main UI thread. 为了解决这个问题,我在新的UI线程上创建了进度条,并在主UI线程上创建了页面。

        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' was my own WPF window for displaying progress information. “ ProgressDialog”是我自己的WPF窗口,用于显示进度信息。

'context' holds the progress data for my progress dialog. “上下文”保存我的进度对话框的进度数据。 It includes a cancelled property so that I can abort the action running on the main thread. 它包括一个canceled属性,以便我可以中止在主线程上运行的操作。 It also includes a complete property so the progress dialog can close when the Action has finished. 它还包括complete属性,因此在Action完成后可以关闭进度对话框。

'Action' is the method used to create all the UI elements. “操作”是用于创建所有UI元素的方法。 It monitors the context for the cancel flag and stops generating the UI elements if the flag is set. 它监视取消标志的上下文,如果设置了标志,则停止生成UI元素。 It sets the complete flag when it is done. 完成时设置完成标志。

I don't remember the exact reason I had to set Thread 't' to an STA thread and IsBackground to true, but I am pretty sure it won't work without them. 我不记得我必须将Thread't'设置为STA线程并将IsBackground设置为true的确切原因,但是我很确定如果没有它们,它将无法正常工作。

If the portions that require the UI thread are relatively small, you can use the Dispatcher to perform those operations without blocking the UI. 如果需要UI线程的部分相对较小,则可以使用Dispatcher来执行那些操作,而不会阻塞UI。 There's overhead associated with this, but it may allow the bulk of the calculations to occur in the background and will interleave the work on the UI thread with other UI tasks. 与此相关的开销很大,但它可能允许大部分计算在后台进行,并且会使UI线程上的工作与其他UI任务交错。 You can update the progress bar with the Dispatcher as well. 您也可以使用分派器更新进度条。

My guess is that everything that is time-consuming to create is within your Visual. 我的猜测是,创建所有耗时的内容都在Visual中。 If so, there is an easy solution: Don't create actual DocumentPage objects and their associated Visuals until DocumentPaginator.GetPage() is called. 如果是这样,那么有一个简单的解决方案:在调用DocumentPaginator.GetPage()之前,不要创建实际的DocumentPage对象及其关联的Visuals。

As long as the code that consumes your document only requests one or two pages at a time there will be no performance bottleneck. 只要占用您文档的代码一次只请求一页或两页,就不会出现性能瓶颈。

If you're printing to the printer or to a file, everything can be done on a background thread, but if you're displaying onscreen you only need to display a few DocumentPages at a time anyway. 如果要打印到打印机或文件,则可以在后台线程上完成所有操作,但是如果要在屏幕上显示,则无论如何一次只需要显示几个DocumentPages。 In either case you won't get any UI lockups. 无论哪种情况,您都不会获得任何UI锁定。

The worst case scenario would be an app that displays pages in a thumbnail view. 最坏的情况是应用程序以缩略图显示页面。 In this case, I would: 在这种情况下,我将:

  1. The thumbnail view would bind its ItemsSource to a "RealizedPages" collection which initially is filled with dummy pages 缩略图视图会将其ItemsSource绑定到“ RealizedPages”集合,该集合最初填充有伪页面
  2. Whenever a dummy page is measured, it queues a dispatcher operation at DispatcherPriority.Background to call DocumentPaginator.GetPage() and then replace the dummy page in the RealizedPages collection with the real page. 每当测量伪页面时,它都会在DispatcherPriority.Background上将调度程序操作排队,以调用DocumentPaginator.GetPage(),然后用实际页面替换RealizedPages集合中的伪页面。

If there are performance concerns even with realizing a single page because of the number of separate items, this same general approach can be used within whatever ItemsControl on the page has the large number of items. 如果由于单个项目的数量而导致即使在实现单个页面时也存在性能问题,则可以页面上具有大量项目的ItemsControl中使用这种相同的通用方法。

One more note: The XPS printing system doesn't ever process more than one DocumentPage at a time, so if you know that's your client you can actually just keep returning the same DocumentPage over and over again with appropriate modifications. 还有一点要注意:XPS打印系统一次不会处理一个以上的DocumentPage,因此,如果您知道那是您的客户,则实际上可以通过适当的修改一遍又一遍地返回相同的DocumentPage。

更详细地说明Ray Burns的答案:您是否可以在后台线程的类中完成数据处理,然后在处理完成后将DocumentPage的属性数据绑定到此类?

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

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