2017-07-09 11 views
-1

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

Performance Profile

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

enter image description here

-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.

+0

WPF ist nicht threadsicher. Sie dürfen UI-gebundene Objekte in anderen Threads nicht ändern. – SLaks

+0

Ich ändere die zugrunde liegende Auflistung, die ein Ereignis "NotifyPropertChanged" auslöst. – NonSecwitter

+0

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. –

Antwort

0

Viele bewegliche Teile, insbesondere auf der Benutzeroberfläche, verlangsamen die Aktualisierung, wenn viele Aktionen/Ereignisse gleichzeitig ausgeführt werden.

Ich sehe keine unmittelbaren Probleme mit dem obigen Code, aber rate einige der Methodenaufrufe Refactoring explizit explizite Abhängigkeiten zu verwenden, vor allem angesichts der asynchronen Natur der Aufrufe, um zu identifizieren, wie UI-abhängige Komponenten interagiert werden.

ExecuteSearchAsync Überarbeitete

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

ClearSearchAsync Überarbeitete

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

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

Ereignishandler Refactoring.

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.FlaggedPeople); 

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

     contextViewModel.Searching = true; 
     await ExecuteSearchAsync(SearchText.Text, contextViewModel.FlaggedPeople, SensitivitySlider.Value/100); 

     contextViewModel.Processing = false; 
    } 
} 
+0

danke für die Hinweise zum Refactoring. Ich möchte die Basisfunktionalität erhalten und für eine Bereinigung zurückkehren, also werde ich zurückkommen und mir das genauer ansehen. – NonSecwitter