【问题标题】:wpf window (ui) blocked by long rendering operation - possible to use background thread for rendering?wpf 窗口(ui)被长渲染操作阻塞 - 可以使用后台线程进行渲染吗?
【发布时间】:2014-10-18 14:29:04
【问题描述】:

渲染操作处于活动状态时,应用程序窗口会阻塞。 IE。设置 ContentControl 的 Content 属性时。绘制作为内容的 DataTemplate 的用户控件。冻结持续 5 到 10 秒,具体取决于所使用的 PC。

这个用户控件不太复杂(大约 250 个简单的控件 - 图像、文本框、文本块、按钮等)。布局远非完美,不是我写的,也没有时间,也不想优化布局,最好能减少问题。

我能做到的最好的方法是将控件包装在一个“容器”中,该容器可以绘制加载动画并在 ui/app 窗口冻结之前显示一个忙碌的光标。 我在下面给出了一个完整的代码清单。

我在包装器自定义控件代码的问题底部的代码中评论了“冻结从这里开始”。这就是 WPF 渲染引擎开始绘制用户控件(即其中的网格)的时候。

我经常使用我最喜欢的搜索引擎,并且了解到 WPF 有一个特殊的“渲染”线程,它与 UI 线程是分开的。

其他在冻结时隐藏应用程序窗口并在此期间显示“加载”动画窗口(或此期间的某种衍生),这很容易给出下面的代码但荒谬 - 有什么方法可以缓解这种情况?

这是代码,首先是用例:

<!-- while I am being rendered, I block the UI thread. -->
<UserControl x:Class="MyUserControl"
             xmlns:loading="clr-namespace:Common.UI.Controls.Loading;assembly=Common.UI.Controls">    
    <loading:VisualElementContainer>
        <loading:VisualElementContainer.VisualElement>
            <Grid>
                <!-- some 500 lines of using other controls with binding, templates, resources, etc.. 
                for the same effect try having a canvas with maaany rectangles..-->
            </Grid>
        </loading:VisualElementContainer.VisualElement>
    </loading:VisualElementContainer>    
</UserControl>

包装器自定义控件布局:

<Style TargetType="{x:Type loading:VisualElementContainer}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type loading:VisualElementContainer}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <loading:LoadingAnimation x:Name="LoadingAnimation" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        <ContentControl x:Name="ContentHost"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

并封装自定义控件代码:

/// <summary>Hosts the visual element and displays a 'loading' animation and busy cursor while it is being rendered.</summary>
public class VisualElementContainer : Control
{
    static VisualElementContainer()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(VisualElementContainer), new FrameworkPropertyMetadata(typeof(VisualElementContainer)));
    }

    private Window MyWindow;
    private ContentControl ContentHost;
    private LoadingAnimation LoadingAnimation;

    public override void OnApplyTemplate()
    {
        this.ContentHost = this.GetTemplateChild("ContentHost") as ContentControl;
        this.LoadingAnimation = this.GetTemplateChild("LoadingAnimation") as LoadingAnimation;

        base.OnApplyTemplate();

        this.MyWindow = this.FindVisualParent(typeof(Window)) as Window;

        this.SetVisual(this.VisualElement);
    }

    private static DependencyProperty VisualElementProperty =
        DependencyProperty.Register(
            "VisualElement",
            typeof(FrameworkElement),
            typeof(VisualElementContainer),
            new PropertyMetadata(null, new PropertyChangedCallback(VisualElementPropertyChanged)));

    public FrameworkElement VisualElement
    {
        get { return GetValue(VisualElementProperty) as FrameworkElement; }
        set { SetValue(VisualElementProperty, value); }
    }

    private static void VisualElementPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var me = sender as VisualElementContainer;

        if (me == null || me.ContentHost == null || me.LoadingAnimation == null)
            return;

        me.RemoveVisual(e.OldValue as FrameworkElement);
        me.SetVisual(e.NewValue as FrameworkElement);
    }

    private void RemoveVisual(FrameworkElement fwElement)
    {
        this.ContentHost.Content = null;

        if (fwElement != null)
            fwElement.Loaded -= fwElement_Loaded;
    }

    private void SetVisual(FrameworkElement fwElement)
    {
        if (fwElement == null)
        {
            this.ContentHost.Content = fwElement;
        }
        else
        {
            fwElement.Loaded += fwElement_Loaded;

            this.SetContentVisibility(false);

            this.Dispatcher
                .BeginInvoke(
                //freeze begins here
                    new Action(() => this.ContentHost.Content = fwElement),
                    System.Windows.Threading.DispatcherPriority.ContextIdle);
        }
    }

    private void fwElement_Loaded(object sender, RoutedEventArgs e)
    {
        this.SetContentVisibility(true);
        //freeze ends here.
    }

    private void SetContentVisibility(bool isContentVisible)
    {
        if (isContentVisible)
        {
            this.MyWindow.Cursor = Cursors.Arrow;

            this.LoadingAnimation.Visibility = Visibility.Collapsed;
            this.ContentHost.Visibility = Visibility.Visible;
        }
        else
        {
            this.MyWindow.Cursor = Cursors.Wait;

            this.ContentHost.Visibility = Visibility.Hidden; //Setting to collapsed results in the loaded event never fireing. 
            this.LoadingAnimation.Visibility = Visibility.Visible;
        }
    }
}

【问题讨论】:

  • 我尝试使用带有 1000 多个按钮的 WrapPanel 作为 VisualElement,它立即出现...是否有任何类似大量数据需要在数据上下文中加载的东西?
  • 我认为没有从源(例如数据库)加载数据。很多被获取的数据是(被)绑定的。周末我将创建一个有一百万个矩形的画布,我希望能做到。
  • 一百万是很多,你一定会从 WPF 中得到这么多的糟糕性能。它并不代表您在用户控件中估计有 250 个控件时遇到的同样问题。
  • 我假设现代 gpu 可以渲染一百万个矩形。这就是我的观点!即使性能很差,如果有办法优雅地处理渲染时间,我也会接受它。一旦控件被渲染,它就会正常运行。它只需要 5-10 秒的冻结时间就可以显示出来!这在企业环境中是不可接受的。
  • 感谢您使用可以重现该问题的工作示例来支持您的问题。

标签: c# wpf rendering dispatcher renderer


【解决方案1】:

我真的不认为您的问题实际上与渲染或布局有关。尤其是只有 250 个控件,因为我看到 wpf 咀嚼 100​​ 倍以上而没有任何问题(它的渲染引擎效率低下,但不是那个效率低下)。除非您使用糟糕的硬件或糟糕的驱动程序滥用丰富的效果(位图效果、不透明蒙版),或者做一些非常奇怪的事情。

考虑您需要的所有数据。是否有要从磁盘加载的大图像或其他大型资源?网络运营?计算时间长?

根据答案,可能会将某些任务推迟到另一个线程。但是如果没有更多信息,我可以建议的唯一解决方案是使用 HostVisual 来嵌套将存在于另一个线程中的控件。不幸的是,这仅适用于非交互式儿童(不需要接收用户输入的儿童)。

【讨论】:

    【解决方案2】:

    据我所知,同时存在渲染线程和 ui 线程,ui 线程应该保持响应,直到渲染完成,然后它(ui 线程)完成更新。听起来好像发生了其他事情,因为 250 个控件并不能解释 5 到 10 秒的等待。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-23
      • 2014-05-09
      相关资源
      最近更新 更多