Ich arbeite an einer kleinen Anwendung, die eine Liste von Personen in einem DataGrid
anzeigt. Ich habe meinen Zeh in den Thread-Pool async
getaucht (punny, eh?) Und das meiste hat gut funktioniert. Ich stoße jetzt auf ein Problem. Ich verstehe die Ursachen des Problems, aber ich bin für eine Lösung ratlos.Async-Operation einfrieren UI
In meinem DataGrid
habe ich ein Style
mit DataTriggers
auf einem Grundstück von meiner Ansicht nach Modell der Visibility
meiner Zeilen zu aktualisieren basiert. Ich verwende die Eigenschaft während einer Levenshtein-Abstandssuche, um festzustellen, ob die Zeilen angezeigt werden. Wenn ich die Suche lösche, setze ich für alle Objekte in der Sammlung die Eigenschaft IsResult
auf true
, um die vollständige Liste erneut anzuzeigen.
Ich bin mir bewusst, dass das Problem die schnelle Layout
Updates der Benutzeroberfläche ist, anstatt den Iterationsprozess selbst. Das habe ich mit Performance Profiling bestätigt. Ich weiß, async/await
ist keine magische Lösung für alle UI-Probleme, also brauche ich nur eine Anleitung, wie Sie diese Operation eleganter verwalten.
Das Projekt ist nicht groß genug, um die Komplexität zu rechtfertigen, die DataGrid
in Virtual
Modus ausgeführt wird, so hoffe ich, es eine andere Lösung ist, ob, wie ich die Ausführung der Suche oder wie DataGrid
corral.
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>
Suchfunktion
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;
}
});
}
löschen Suchfunktion
private async Task ClearSearchAsync()
{
ObservableCollection<FlaggedPersonViewModel> searchBase = contextViewModel.FlaggedPeople;
await Task.Run
(() =>
{
foreach (FlaggedPersonViewModel person in searchBase)
person.IsResult = true;
});
}
Suchoperationen Handler aufrufen
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;
}
}
Leistungsprofil
EDIT
I entfernt await Task.Run
aus der ClearSearchAsync
Funktion, den Vorgang auf der Haupt-Thread ausgeführt werden. Es scheint, die Leistung weiter verringert zu haben.
1.42s in der Asynchron-Lauf, 2,39 im Synchronlauf
-Update 2017.07.14 Im Moment habe ich Zuflucht, nur um die Abfrage erneut ausgeführt und eine frische Sammlung ins Netz werfen. Ich bin nicht wirklich zufrieden damit, wie es scheint, einen Vorschlaghammer für einen Detailjob zu verwenden.
WPF ist nicht threadsicher. Sie dürfen UI-gebundene Objekte in anderen Threads nicht ändern. – SLaks
Ich ändere die zugrunde liegende Auflistung, die ein Ereignis "NotifyPropertChanged" auslöst. – NonSecwitter
Führen Sie alle UI-Aktualisierungen gleichzeitig im UI-Thread aus. Zum Beispiel ist die 'Task.Run' in' ClearSearchAsync' völlig nutzlos und kontraproduktiv: Die einzige kostspielige Operation ist 'person.IsResult = true;', was einen Wechsel zurück zum UI-Thread erfordert. –