【问题标题】:Manually controlling window or canvas rendering?手动控制窗口或画布渲染?
【发布时间】:2012-01-04 16:33:36
【问题描述】:

情况

我有一个应用程序可以计算多个元素的恒定变化位置(通常同时在 10 到 20 个之间)。计算在专用线程上完成,然后触发一个事件以指示计算已完成。这个专用线程以每秒 100 帧的稳定速度运行。

可视化

元素的可视化是使用 WPF 完成的。我实现了一个画布并编写了自定义代码以将每个元素的可视化表示添加到画布。使用 Canvas.SetLeftCanvas.SetTop 执行定位。此代码在专用线程上的事件处理程序内运行,因此不会影响性能。

问题

由于元素不断移动,因此移动似乎卡顿。目前,我唯一能做的假设是 WPF 以自己的方式呈现并尝试达到每秒 60 帧的最大值。如果您有其他关于为什么会发生这种情况的 cmets,请赐教。

问题

当专用线程调用完成的计算时,我如何告诉 WPF 进行渲染?如果可能的话,我想在计算完成之前阻止渲染窗口/画布,此时框架应该前进并渲染新信息。

评论

很多人不喜欢超过默认的每秒 60 帧的尝试,我也不喜欢。但是,在渲染发生之前, 能够影响渲染是绝对必要的下降到每秒 60 帧。这是为了确保每秒 60 帧不会影响卡顿问题。

渲染(代码)

根据 Andrew Burnett-Thompson 博士的要求,这是实际布局更新所涉及的代码。 MainViewModel 包含一个带有 ActorViewModel 实例的 ObservableCollection,由专用工作线程创建。然后将这些转换为 ActorView 实例以可视化计算的数据。在添加和删除实例时,我有意使用 Invoke,而不是 BeginInvoke 来确保这些例程不是导致性能问题的原因。

在完成专用工作者计算后,在 MainViewModel 中调用 UpdateLayout,此时调用 UpdateSynchronizationCollectionLayout 来更新可视化。此时,未使用缩放和不透明度,并且在省略实例 Viewbox 时观察到相同的卡顿行为。除非我遗漏了什么,否则问题应该与我无法检查或控制渲染时间或速度这一事实有关。

    /// <summary>
    /// Contains the added ActorViewModel instances and the created ActorView wrapped in a ViewBox.
    /// </summary>
    private Dictionary<ActorViewModel, KeyValuePair<Viewbox, ActorView>> _hSynchronizationCollection = new Dictionary<ActorViewModel, KeyValuePair<Viewbox, ActorView>>();

    /// <summary>
    /// Update the synchronization collection with the modified data.
    /// </summary>
    /// <param name="sender">Contains the sender.</param>
    /// <param name="e"></param>
    private void _UpdateSynchronizationCollection( object sender, NotifyCollectionChangedEventArgs e )
    {
        // Check if the action that caused the event is an Add event.
        if ( e.Action == NotifyCollectionChangedAction.Add )
        {
            // Invoke the following code on the UI thread.
            Dispatcher.Invoke( new Action( delegate()
            {
                // Iterate through the ActorViewModel instances that have been added to the collection.
                foreach( ActorViewModel hActorViewModel in e.NewItems )
                {
                    // Initialize a new _hInstance of the ActorView class.
                    ActorView hActorView = new ActorView( hActorViewModel );

                    // Initialize a new _hInstance of the Viewbox class.
                    Viewbox hViewBox = new Viewbox { StretchDirection = StretchDirection.Both, Stretch = Stretch.Uniform };

                    // Add the _hInstance of the ActorView to the synchronized collection.
                    _hSynchronizationCollection.Add( hActorViewModel, new KeyValuePair<Viewbox, ActorView>( hViewBox, hActorView ));

                    // Set the child of the Viewbox to the ActorView.
                    hViewBox.Child = hActorView;

                    // Add the _hInstance of the ActorView to the canvas.
                    CanvasDisplay.Children.Add( hViewBox );
                }
            }));
        }
        // Check if the action that caused the event is a Remove event.
        else if ( e.Action == NotifyCollectionChangedAction.Remove )
        {
            // Invoke the following code on the UI thread.
            Dispatcher.Invoke( new Action( delegate()
            {
                // Iterate through the ActorViewModel instances that have been removed to the collection.
                foreach( ActorViewModel hActorViewModel in e.OldItems )
                {
                    // Check if the ActorViewModel _hInstance is contained in the synchronization collection.
                    if ( _hSynchronizationCollection.ContainsKey( hActorViewModel ))
                    {
                        // Remove the ActorView from the canvas.
                        CanvasDisplay.Children.Remove( _hSynchronizationCollection[hActorViewModel].Key );

                        // Remove the ActorViewModel from the collection.
                        _hSynchronizationCollection.Remove( hActorViewModel );
                    }
                }
            }));
        }
    }

    /// <summary>
    /// Update the synchronization collection layout with the modified data.
    /// </summary>
    private void _UpdateSynchronizationCollectionLayout()
    {
        // Invoke the following code on the UI thread.
        Dispatcher.Invoke( new Action( delegate()
        {
            // Iterate through each ActorViewModel in the synchronization collection.
            foreach( KeyValuePair<ActorViewModel, KeyValuePair<Viewbox, ActorView>> hDictionaryKeyValuePair in _hSynchronizationCollection )
            {
                // Retrieve the ActorViewModel.
                ActorViewModel hActorViewModel = hDictionaryKeyValuePair.Key;

                // Retrieve the KeyValuePair for this ActorViewModel.
                KeyValuePair<Viewbox, ActorView> hKeyValuePair = hDictionaryKeyValuePair.Value;

                // Sets the height of the ViewBox in which the ActorView is displayed.
                hKeyValuePair.Key.Height = hKeyValuePair.Value.ActualHeight * hActorViewModel.LayoutScale;

                // Sets the width of the ViewBox in which the ActorView is displayed.
                hKeyValuePair.Key.Width = hKeyValuePair.Value.ActualWidth * hActorViewModel.LayoutScale;

                // Set the opacity factor of the ActorView.
                hKeyValuePair.Value.Opacity = hActorViewModel.LayoutOpacity;

                // Sets the hValue of the Left attached property for the given dependency object.
                Canvas.SetLeft( hKeyValuePair.Key, hActorViewModel.LayoutLeft - ( hActorViewModel.LayoutAlignment == MainAlignment.Center ? hKeyValuePair.Key.ActualWidth / 2 : ( hActorViewModel.LayoutAlignment == MainAlignment.Right ? hKeyValuePair.Key.ActualWidth : 0 )));

                // Sets the hValue of the Top attached property for the given dependency object.
                Canvas.SetTop( hKeyValuePair.Key, hActorViewModel.LayoutTop );

                // Sets the hValue of the ZIndex attached property for the given dependency object.
                Canvas.SetZIndex( hKeyValuePair.Key, hActorViewModel.LayoutLayerIndex );
            }
        }));
    }

    /// <summary>
    /// Initialize a new _hInstance of the MainWindow class.
    /// </summary>
    /// <param name="hMainViewModel">Contains the object that is used as data context in MainView.</param>
    internal MainView( MainViewModel hMainViewModel )
    {
        // Add a subscriber that occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
        hMainViewModel.ActorViewModel.CollectionChanged += new NotifyCollectionChangedEventHandler( _UpdateSynchronizationCollection );

        // Initialize the component.
        InitializeComponent();

        // Set the subscriber that occurs when the layout changes.
        hMainViewModel.LayoutChanged += new Action( _UpdateSynchronizationCollectionLayout );
    }

【问题讨论】:

  • +1 表示措辞出色的 Q :) 你不应该在 60FPS 时出现口吃。人眼只能检测到 25FPS(或更低)。你能发布一些代码sn-ps吗,因为这里可能有很多东西(没有双关语;P)
  • @Dr.AndrewBurnett-Thompson 感谢您的友好评论 :) 根据请求更新了“渲染”部分的问题。我希望这将改善当前的情况和问题。
  • 没问题 Roel,这就是它的主题,知识共享。这里有一些不那么善良的角色;-)

标签: .net wpf performance rendering


【解决方案1】:

默认情况下,您的应用程序代码在 UI 线程上运行,换句话说,如果您编写一个循环,通过其画布坐标更新所有对象的位置,则 UI 不会重新渲染,直到您的循环退出。您需要将计算“原子”发送到 UI 线程,然后一次性更新所有对象。

你提到:

使用 Canvas.SetLeft 和 Canvas.SetTop 执行定位。这 代码在专用线程上的事件处理程序中运行,并且 因此不会影响性能。

我认为这是包装在 Dispatcher.BeginInvoke 中以将其编组到 UI 线程?

【讨论】:

  • 根据另一个请求更新了“渲染”部分的问题。如您所见,我正在使用 Invoke 将更新编组到正确的线程,但是,更改似乎不会直接生效,而是 WPF 保持自己的渲染速度和时间。
  • 没错,它们不会立即生效,渲染是由框架在未来的某个时间点执行的。当渲染发生时,您可以通过 CompositionTarget.Rendering 事件 (msdn.microsoft.com/en-us/library/…) 接收通知,并且您可以在此事件处理程序中更改您的 UI。
  • 我编写了一些代码来跟踪从专用线程调度到实际渲染(使用此事件)的时间(以滴答为单位),并得出平均延迟为 4~5 毫秒的结论.这会降低 50% 的性能,因此渲染将以每秒约 50-55 帧的速度进行。这解释了口吃。我没有办法影响这个吗?
猜你喜欢
  • 2012-08-15
  • 2016-08-06
  • 2019-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多