使用async / await加载usercontrols

时间:2018-06-02 10:29:02

标签: c# wpf asynchronous user-controls async-await

我必须在面板中加载许多用户控件。如何使用async / await方法为延迟加载添加它而不冻结ui?

foreach (var item in parsedValues)
{
    _addUser = new MaterialCircular(item.Title,item.Category,item.Type,item.SubType,item.Date, AppVariable.GetBrush(Convert.ToString(FindElement.Settings[AppVariable.ChartColor] ?? AppVariable.CHART_GREEN)));
    _currentUser = _addUser;
    waterfallFlow.Children.Add(_currentUser);
}

1 个答案:

答案 0 :(得分:1)

一般来说,没有一种特殊的“等待”方法可以将控件加载到UI中,因此可以在后台完成并解冻UI。最终,所有UI更新都按顺序在主UI线程上执行。话虽这么说,你可以有多个线程和/或任务(不是说两者都是相同的)通过UI Dispatcher向UI发布更改。

加载大量控件肯定会使主UI线程超载,以至于UI会在WPF处理工作负载时冻结。

这是一个潜在的解决方案,展示了如何使用等待的任务通过调度程序加载(和限制)所有控件,而不会冻结UI。对社区反馈也非常感兴趣,因为我过去一直在努力解决这个问题。

此解决方案需要您进行实验和调整。这是基于以下观察:WPF在添加大量子控件方面肯定很慢,并且假设 UI冻结是由于UI线程忙于处理添加控件而不是处理鼠标事件;所以这个解决方案基于限制。

的Xaml:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="300" Width="500">
<DockPanel LastChildFill="True">
    <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
        <Button Content="Load Controls" Click="Button1_Click"></Button>
        <Label>Status:</Label>
        <Label Name="lbl_Status"></Label>
    </StackPanel>

    <ScrollViewer Name="scroll">
        <StackPanel Orientation="Vertical" Name="panel"></StackPanel>
    </ScrollViewer>
</DockPanel>

和关联的C#,加载10,000个文本框

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;


namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        private volatile bool _waitForDispatcher = false;
        public MainWindow()
        {
            InitializeComponent();

            // we use this hook to detect when the dispatcher queue gets empty
            // so we can better adjust our throttling to add the controls to the panel
            this.Dispatcher.Hooks.DispatcherInactive += (object s1, EventArgs e1) =>
            {
                this._waitForDispatcher = false;
            };
        }

        private async void Button1_Click(object sender, RoutedEventArgs e)
        {
            // doesn't have to be awaited if that's all this click handler does
            // but if this function needs to do other things to the UI after this task completes 
            // then you definitely must await this task.
            await Task.Run(() =>
            {
                _waitForDispatcher = true;

                for (int i = 0; i< 10000; i++)
                {
                    this.Dispatcher.Invoke(() => {
                        // add the control.
                        TextBox tb = new TextBox() { Text = "Text box " + i, Width = 100, Height = 22, Margin = new Thickness(0, 0, 0, 10) };
                        this.panel.Children.Add(tb);
                        this.lbl_Status.Content = "Added item " + i; // will provide a good indicator on the screen for UI freeze, especially as you resize the app
                    });

                    // throttle: for every 50 controls that we add, we wait for the dispatcher queue to clear.
                    // but limit that to 50 ms so floods of mouse events don't take over and prevent the controls from loading
                    if ((i % 50) == 0)
                    {
                        int j = 0;
                        while (_waitForDispatcher && j++ < 12) // limit to 50 ms to make sure we don't let busy mouse events block it all.
                        {
                            Thread.Sleep(5); // tweak this. Gives time for the dispatcher queue to flush
                        }
                        Console.WriteLine("Waited " + (j * 5) + " ms for dispatcher");
                        _waitForDispatcher = true;
                    }
                }
            });
        }
    }
}

对社区的建设性反馈非常感兴趣。注意:Dispatcher.BeginInvoke()比Dispatcher.Invoke()差得多 - 万一人们想知道。