异步操作冻结UI

时间:2017-07-09 14:18:32

标签: c# wpf asynchronous datagrid async-await

我正在开发一个小应用程序,该应用程序显示DataGrid中的人员列表。我已经把我的脚趾浸在async线程池(punny,嗯?)中,大部分都运行良好。但是,我现在遇到了一个问题。我理解问题的根源,但我很难找到解决方案。

在我的DataGrid中,我有Style DataTriggers根据我的视图模型的属性更新行的Visibility。我在Levenshtein距离搜索期间使用该属性来确定是否将显示行。当我清除搜索时,我会在集合中的所有对象上将属性IsResult设置为true,以重新显示完整列表。

我知道问题是UI的快速Layout更新,而不是迭代过程本身。我已通过性能分析证实了这一点。我知道async/await并不是解决所有UI问题的神奇解决方案,因此我需要就如何更优雅地管理此操作提出一些指导。

该项目不够大,无法保证在DataGrid模式下运行Virtual的复杂性,所以我希望有另一种解决方案,无论我是如何进行的。 ; m执行搜索或如何围绕DataGrid

数据网格

    <Style x:Key="EditableDataGrid" TargetType="DataGrid">
        <Setter Property="IsReadOnly" Value="True"/>
        <Setter Property="AutoGenerateColumns" Value="False"/>
        <Setter Property="RowBackground" Value="WhiteSmoke"/>
        <Setter Property="AlternatingRowBackground" Value="AntiqueWhite"/>
        <EventSetter Event="MouseDoubleClick" Handler="GridDoubleClick"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=Editing}" Value="True">
                <Setter Property="IsReadOnly" Value="False"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=Adding}" Value="True">
                <Setter Property="IsReadOnly" Value="False"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <DataGrid Name="FlaggedPersonDataGrid"
              Grid.Column="0" 
              ItemsSource="{Binding FlaggedPeople}" 
              Style="{StaticResource EditableDataGrid}">

        <DataGrid.RowStyle>
            <Style TargetType="DataGridRow">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=IsResult}" Value="False">
                        <Setter Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </DataGrid.RowStyle>

        <DataGrid.Columns>
            <DataGridTextColumn Width="*" Header="Last Name" 
                                Binding="{Binding LastName, UpdateSourceTrigger=LostFocus}"/>
            <DataGridTextColumn Width="*" Header="First Name" 
                                Binding="{Binding FirstName, UpdateSourceTrigger=LostFocus}"/>
        </DataGrid.Columns>

        <DataGrid.Resources>
            <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
                             Color="#FF3399FF"/>
        </DataGrid.Resources>

    </DataGrid>

搜索功能

    private async Task ExecuteSearchAsync()
    {

        string searchTerm = SearchText.Text;
        double lastNameScore, firstNameScore, distanceScore, searchSensitivity;

        ObservableCollection<FlaggedPersonViewModel> searchBase = contextViewModel.FlaggedPeople;

        searchSensitivity = SensitivitySlider.Value / 100;

        await Task.Run
            (() =>
            {
                foreach (FlaggedPersonViewModel person in searchBase)
                {
                    lastNameScore = GetLevenshteinDistance(searchTerm, person.LastName, false);
                    lastNameScore = (person.LastName.Length - lastNameScore) / person.LastName.Length;

                    firstNameScore = GetLevenshteinDistance(searchTerm, person.FirstName, false);
                    firstNameScore = (person.FirstName.Length - firstNameScore) / person.FirstName.Length;

                    distanceScore = System.Math.Max(firstNameScore, lastNameScore);

                    if (distanceScore > searchSensitivity)
                        person.IsResult = true;
                    else
                        person.IsResult = false;
                }
            });
    }

清除搜索功能

    private async Task ClearSearchAsync()
    {
        ObservableCollection<FlaggedPersonViewModel> searchBase = contextViewModel.FlaggedPeople;

        await Task.Run
            (() =>
            {
                foreach (FlaggedPersonViewModel person in searchBase)
                    person.IsResult = true;
            });
    }

处理程序调用搜索操作

    private async void Search_Click(object sender, RoutedEventArgs e)
    {
        if (contextViewModel.Searching)
        {
            contextViewModel.Processing = true;

            //Already searching, revert to clear state
            contextViewModel.Searching = false;
            await ClearSearchAsync();

            contextViewModel.Processing = false;
        }
        else
        {
            contextViewModel.Processing = true;

            contextViewModel.Searching = true;
            await ExecuteSearchAsync();

            contextViewModel.Processing = false;
        }
    }

效果资料

Performance Profile

修改

我从await Task.Run函数中删除了ClearSearchAsync,以便在主线程上运行该进程。它似乎进一步降低了性能。

异步运行时为1.42秒,同步运行中为2.39

enter image description here

2017年7月14日更新 目前,我只是尝试重新运行查询并将新的集合投入网格中。我对此并不满意,因为看起来好像是用大锤做细节工作。

1 个答案:

答案 0 :(得分:0)

当许多动作/事件试图同时执行时,特别是在UI上的很多移动部件往往会减慢更新速度。

我发现上述代码没有立即出现问题,但建议重构一些方法调用以使用显式依赖项,特别是考虑到调用的异步性质,以便识别与UI相关的组件如何与之交互。

ExecuteSearchAsync Refactored

set makeprg=javac\ %

ClearSearchAsync Refactored

private async Task ExecuteSearchAsync(string searchTerm, ObservableCollection<FlaggedPersonViewModel> searchBase, double searchSensitivity) {
    double lastNameScore, firstNameScore, distanceScore;
    await Task.Run
    (() => {
        foreach (FlaggedPersonViewModel person in searchBase) {
            lastNameScore = GetLevenshteinDistance(searchTerm, person.LastName, false);
            lastNameScore = (person.LastName.Length - lastNameScore) / person.LastName.Length;

            firstNameScore = GetLevenshteinDistance(searchTerm, person.FirstName, false);
            firstNameScore = (person.FirstName.Length - firstNameScore) / person.FirstName.Length;

            distanceScore = System.Math.Max(firstNameScore, lastNameScore);

            if (distanceScore > searchSensitivity)
                person.IsResult = true;
            else
                person.IsResult = false;
        }
    });
}

重构事件处理程序。

private async Task ClearSearchAsync(ObservableCollection<FlaggedPersonViewModel> searchBase) {

    await Task.Run
        (() => {
            foreach (FlaggedPersonViewModel person in searchBase)
                person.IsResult = true;
        });
}