Окно wpf (ui) заблокировано длительной операцией рендеринга - можно ли использовать фоновый поток для рендеринга?

Окно приложения блокируется, пока активна операция рендеринга. т.е. когда установлено свойство Content элемента управления ContentControl. Рисуется пользовательский элемент управления, который является DataTemplate для содержимого. Замораживание длится от 5 до 10 секунд в зависимости от используемого ПК.

Этот пользовательский элемент управления не слишком сложен (около 250 простых элементов управления — изображения, текстовые поля, текстовые блоки, кнопки и т. д.). Вёрстка далека от совершенства, её писал не я, да и времени на оптимизацию вёрстки нет, да и не хочется, так как проблему можно было бы в лучшем случае уменьшить.

Лучшее, что мне удалось сделать, это обернуть элемент управления в «контейнер», который умеет рисовать анимацию загрузки и показывать занятый курсор перед зависанием окна пользовательского интерфейса/приложения. Ниже я привожу полный листинг кода.

Я прокомментировал «замораживание начинается здесь» в коде внизу вопроса в коде пользовательского элемента управления оболочки. Именно тогда механизм рендеринга WPF начинает рисовать пользовательский элемент управления (то есть сетку внутри него).

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

Другое, что скрывает окно приложения, пока оно заморожено, и отображает окно анимации «загрузки» в это время (или что-то производное от этого), что легко, учитывая приведенный ниже код, но абсурдно - есть ли способ смягчить ситуацию?

Вот код, сначала вариант использования:

<!-- 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;
        }
    }
}

person h.alex    schedule 25.08.2014    source источник
comment
Я пробовал с WrapPanel с 1000+ кнопок в качестве VisualElement, и он появляется мгновенно... Есть ли что-то вроде большого количества данных, которые нужно где-то загрузить в контекстах данных?   -  person Roel van Westerop    schedule 28.08.2014
comment
Я думаю, что данные не загружаются из источника (например, из базы данных). Тем не менее, многие данные, которые извлекаются, связаны. На выходных создам канву с миллионом прямоугольников, надеюсь, что справлюсь.   -  person h.alex    schedule 29.08.2014
comment
Миллион - это много, с такой суммой вы получите плохую производительность от WPF. И это не представляет собой ту же проблему, которую вы получаете с примерно 250 элементами управления в вашем пользовательском элементе управления.   -  person Roel van Westerop    schedule 29.08.2014
comment
Я бы предположил, что современный графический процессор может отображать миллион прямоугольников. Это моя точка зрения! Даже если производительность плохая, я бы смирился с этим, если бы был способ изящно справиться со временем рендеринга. Как только элемент управления визуализируется, он ведет себя нормально. Для его отображения достаточно 5-10-секундной заморозки! Это неприемлемо в корпоративной среде.   -  person h.alex    schedule 29.08.2014
comment
признателен, если вы можете поддержать свой вопрос рабочим образцом, который может воспроизвести проблему.   -  person pushpraj    schedule 29.08.2014
comment
Профилируйте это! Используйте Performance Toolkit, чтобы узнать, является ли поток пользовательского интерфейса настолько загруженным. Он также сообщит вам о избыточных анимациях, триггерах и т. д. ВСЕГДА сначала измеряйте перед оптимизацией, чтобы не внедрять решения, которые не помогают. еще один быстрый трюк - отслеживать дисковую и сетевую активность в случае, если файл/база данных запрашивается потоком пользовательского интерфейса...   -  person Emond Erno    schedule 30.08.2014
comment
вы пробовали замораживать свои фризы? stackoverflow.com/questions/799911/ Также проверьте это wpftutorial.net/10PerformanceTips.html< /а>   -  person Arie    schedule 03.09.2014
comment
Сообщает ли Visual Studio о каких-либо сбоях привязки в окне вывода, когда оно начинает зависать? Специально привязки с FindAncestor?   -  person dharshana jagoda    schedule 04.09.2014


Ответы (2)


Я действительно не думаю, что ваша проблема на самом деле связана с рендерингом или макетом. Особенно с 250 элементами управления, так как я видел, как wpf без проблем жует в 100 раз больше (его механизм рендеринга неэффективен, но не настолько неэффективен). Если только вы не злоупотребляете богатыми эффектами (растровые эффекты, маска непрозрачности) с плохим оборудованием или плохим драйвером, или делаете что-то действительно странное.

Учитывайте все необходимые данные. Есть ли большие изображения или другие большие ресурсы для загрузки с диска? Сетевые операции? Долгие вычисления?

В зависимости от ответа может быть возможно перенести некоторые задачи на другой поток. Но без дополнительной информации единственным решением, которое я могу предложить, является использование HostVisual для вложения элементов управления, которые будут жить в другом потоке. К сожалению, это подходит только для неинтерактивных детей (детей, которым не нужно получать пользовательские данные).

person Victor Victis    schedule 02.09.2014

Из того, что я знаю, есть как поток рендеринга, так и поток пользовательского интерфейса, поток пользовательского интерфейса должен оставаться отзывчивым до тех пор, пока рендеринг не завершится, а затем он (поток пользовательского интерфейса) завершает обновление. Похоже, что происходит что-то еще, поскольку 250 элементов управления не объясняют 5-10-секундное ожидание.

person Steve    schedule 30.08.2014