November 2015

Band 30, Nummer 12

Windows 10 – Beschleunigen von Dateivorgängen mithilfe der Indexerstellung für die Suche

Von Adam Wilson | November 2015

Die Indexerstellung für die Suche ist Bestandteil der letzten Windows-Versionen und die allgemeine Grundlage der Bibliotheksansichten im Datei-Explorer sowie der Internet Explorer-Adressleiste und bietet Suchfunktionalität im Startmenü und für Outlook. Unter Windows 10 steht die Leistungsfähigkeit des Indexers nicht mehr nur Desktopcomputern, sondern allen Apps auf der universellen Windows-Plattform(UWP) zur Verfügung. Wenngleich dadurch auch Cortana bessere Suchvorgänge ermöglicht werden, ist das vielversprechendste an dieser Weiterentwicklung die weitaus bessere Interaktion von Anwendungen mit dem Dateisystem.

Der Indexer ermöglicht Apps wesentlich interessantere Vorgänge, z. B. das Sortieren und Gruppieren von Dateien und Nachverfolgen von Änderungen am Dateisystem. Die meisten Indexer-APIs stehen UWP-Apps über die Namespaces "Windows.Storage" und "Windows.Storage.Search" zur Verfügung. Apps nutzen bereits den Indexer, um den Benutzern überzeugende Erfahrungen zu bieten. In diesem Artikel wird erläutert, wie Sie mithilfe des Indexers Änderungen am Dateisystem nachverfolgen und Ansichten schnell rendern können. Außerdem erfahren Sie in Form einfacher Tipps, wie Sie die Abfragen einer Anwendung verbessern können.

Schneller Zugriff auf Dateien und Metadaten

Die meisten Benutzergeräte enthalten Hunderte oder Tausende von Mediendateien, zu denen auch die Lieblingsbilder -und musiktitel der Benutzer gehören. Apps, die die Dateien auf einem Gerät schnell durchlaufen und anregende Interaktionen mit den Dateien ermöglichen, gehören zu den beliebtesten auf allen Plattformen. Die UWP bietet verschiedene Klassen, über die auf Dateien auf sämtlichen Geräten unabhängig vom Formfaktor zugegriffen werden kann.

Der "Windows.Storage"-Namespace bietet die Basisklassen für den Zugriff auf Dateien und Ordner sowie die Basisvorgänge, die die meisten Apps darauf anwenden. Doch wenn Ihre App auf viele Dateien oder Metadaten zugreifen muss, bieten diese Klassen nicht die von den Benutzern verlangten Leistungsmerkmale.

Beispielweise ist das Aufrufen von "StorageFolder.GetFilesAsync" ein Weg in die völlig falsche Richtung, wenn Sie nicht den Ordner steuern, den Sie aufzählen. Benutzer können Milliarden von Dateien in einem einzigen Verzeichnis ablegen, doch der Versuch, für jede davon "StorageFile"-Objekte zu erstellen, führt dazu, dass der Arbeitsspeicher einer App sehr schnell zur Neige geht. Selbst in weniger extremen Fällen erfolgt die Rückgabe auf den Aufruf sehr langsam, da das System Tausende von Dateihandles erstellen und diese zurück in den App-Container marshallen muss. Damit Apps dieser Falle entgehen, bietet das System die Klassen "StorageFileQueryResults" und "StorageFolderQueryResults".

"StorageFileQueryResults" ist die unumgängliche Klasse, wenn eine Anwendung geschrieben wird, die mehr als nur eine Handvoll Dateien bewältigen soll. Die Klasse bietet nicht nur eine bequeme Möglichkeit, die Ergebnisse einer komplexen Suchabfrage aufzuzählen und zu ändern, sondern bietet sich auch, da die API die Aufzählungsanforderung als eine Abfrage nach "*" behandelt, für profanere Fälle an.

Das Verwenden des Indexers, wann immer verfügbar, ist der erste Schritt zum Beschleunigen Ihrer App. Da diese Worte vom Programm-Manager des Indexers stammen, hört sich das erst einmal nach dem eigennützigen Wunsch nach Fortsetzung des Beschäftigungsverhältnisses an, doch es steckt ein logischer Grund dahinter. Die Objekte "StorageFile" und "StorageFolder" wurden mit Blick auf den Indexer gestaltet. Die im Objektcache zwischengespeicherten Eigenschaften können schnell aus dem Indexer abgerufen werden. Wenn Sie den Indexer nicht nutzen, muss das System Werte auf dem Datenträger und in der Registrierung nachschlagen, was E/A-intensiv ist und Leistungsprobleme für App und System verursacht.

Um sicherzustellen, dass der Indexer verwendet wird, erstellen Sie ein "Query­Options"-Objekt. Legen Sie die "QueryOptions.IndexerOption"-Eigenschaft so fest, dass entweder nur der Indexer verwendet wird:

QueryOptions options = new QueryOptions();
options.IndexerOption = IndexerOption.OnlyUseIndexer;

oder der Index verwendet wird, sofern er verfügbar ist:

options.IndexerOption = IndexerOption.UseIndexerWhenAvailable;

Die empfohlene Nutzung sieht für Fälle, bei denen ein langsamer Dateivorgang Ihre App nicht blockiert oder die Benutzererfahrung stört, die Verwendung von "IndexerOption.Use­IndexerWhenAvailable" vor. Dabei wird versucht, den Indexer zum Aufzählen von Dateien zu nutzen, doch bei Bedarf wird auf die wesentlich langsamere Datenträgervorgänge zurückgegriffen. "IndexerOption.OnlyUseIndexer" wird am besten verwendet, wenn die Rückgabe keiner Ergebnisse besser als das Abwarten eines langsamen Dateivorgangs ist. Das System gibt keine Ergebnisse zurück, wenn der Indexer deaktiviert ist, bietet aber eine schnelle Rückgabe und lässt weiter zu, dass Apps auf den Benutzer reagieren.

Es gibt Situationen, in denen das Erstellen eines "QueryOptions"-Objekts für bloß eine schnelle Aufzählung ein wenig viel Aufwand zu sein scheint. In diesen Fällen ist es sinnvoll, sich nicht zu fragen, ob der Indexer vorhanden ist. In Fällen, in denen Sie den Inhalt des Ordners steuern, kann das Aufrufen von "StorageFolder.GetItemsAsync" sinnvoll sein. Es ist eine einfach zu schreibende Codezeile, und Leistungsprobleme sind nicht spürbar, wenn das Verzeichnis nur einige wenige Dateien enthält.

Eine weitere Möglichkeit der Beschleunigung der Dateiaufzählung ist der Verzicht auf das Erstellen unnötiger "StorageFile"- oder "StorageFolder"-Objekte. Selbst bei Verwenden des Indexers erfordert das Öffnen eines "StorageFile"-Objekts vom System das Erstellen eines Dateihandles, Erfassen von Eigenschaftsdaten und deren Marshalling in den Prozess der App. Diese Kommunikation zwischen Prozessen (IPC) weist inhärente Verzögerungen auf, die in vielen Fällen vermieden werden können, indem die Objekte erst gar nicht erstellt werden.

Ein wichtiger Hinweis ist, dass ein vom Indexer unterstütztes "StorageFileQueryResult"-Objekt intern keine "StorageFile"-Objekte erstellt. Diese werden bedarfsabhängig nach Anforderung durch "GetFilesAsync" erstellt. Bis zu diesem Zeitpunkt speichert das System nur eine Liste der Dateien im Arbeitsspeicher, die vergleichsweise schlank ist.

Für das Aufzählen sehr vieler Dateien wird die Batchverarbeitungsfunktionalität von "GetFilesAsync" empfohlen, um Gruppen von Dateien bedarfsabhängig in Seiten einzuteilen. Auf diese Weise kann Ihre App eine Hintergrundverarbeitung auf die Dateien anwenden, während diese auf das Erstellen der nächsten Gruppe wartet. Der Code in Abbildung 1 zeigt, wie das bei einem einfachen Beispiel erfolgt.

Abbildung 1: GetFilesAsync

uint index = 0, stepSize = 10;
IReadOnlyList<StorageFile> files = await queryResult.GetFilesAsync(index, stepSize);
index += 10;          
while (files.Count != 0)
{
  var fileTask = queryResult.GetFilesAsync(index, stepSize).AsTask();
  foreach (StorageFile file in files)
  {
    // Do the background processing here   
  }
  files = await fileTask;
  index += 10;
}

Dies ist dasselbe Programmiermuster, das bereits für verschiedene unter Windows ausgeführte Apps verwendet wurde. Durch Variieren der Schrittgröße können sie die richtige Anzahl von Elementen abrufen, um eine erste nützlich Ansicht in der App zu generieren, während der Rest der Dateien im Hintergrund rasch aufbereitet wird.

Der Vorabruf von Eigenschaften ist eine weitere einfache Möglichkeit zur Beschleunigung Ihrer App. Dabei lässt Ihre App das System wissen, dass sie an einer bestimmten Gruppe von Dateieigenschaften interessiert ist. Das System ruft diese Eigenschaften aus dem Indexer ab, während es eine Gruppe von Dateien aufzählt, und speichert sie im "StorageFile"-Objekt zwischen. Dies ermöglicht einen Leistungszuwachs ohne viel Aufwand im Vergleich zum fragmentarischen Erfassen der Eigenschaften, wenn die Dateien zurückgegeben werden.

Die Werte für den Vorabruf von Eigenschaften legen Sie im "QueryOptions"-Objekt fest. Einige gängige Szenarien werden bei Verwenden von "Property­PrefetchOptions" unterstützt, doch Apps können auch die angeforderten Eigenschaften in beliebige von Windows unterstützte Werte anpassen. Der erforderliche Code ist einfach:

QueryOptions options = new QueryOptions();
options.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties,
  new String[] { });

In diesem Fall nutzt die App Bildeigenschaften und benötigt keine weitere Eigenschaften. Wenn das System die Ergebnisse der Abfrage aufzählt, werden die Bildeigenschaften im Arbeitsspeicher zwischengespeichert, sodass sie später schnell verfügbar sind.

Ein letzter Hinweis ist, dass die Eigenschaft im Index gespeichert sein muss, damit der Vorabruf einen Leistungszuwachs bringt. Andernfalls muss das System auf die Datei zugreifen, um den Wert zu finden, was vergleichsweise sehr langsam ist. Die Microsoft Windows Dev Center-Seite mit dem Eigenschaftensystem (bit.ly/1LuovhT) bietet sämtliche Informationen zu den Eigenschaften, die für den Windows-Indexer zur Verfügung stehen. Sie müssen in der Beschreibung der Eigenschaft bloß nach "isColumn = true" suchen, um herauszufinden, ob die Eigenschaft für den Vorabruf verfügbar ist.

Wenn Sie alle diese Verbesserungen nutzen, läuft Ihr Code wesentlich schneller. Als einfaches Beispiel habe ich eine App geschrieben, die alle Bilder auf meinem Computer samt ihrer Höhe abruft. Dies ist der erste Schritt in einer App zur Anzeige von Fotos, der erfolgen muss, um die Fotosammlung des Benutzers anzuzeigen.

Ich habe drei Läufe ausgeführt, um verschiedene Arten der Dateiaufzählung auszuprobieren und die Unterschiede dazwischen aufzuzeigen. Beim ersten Test wurde naiver Code bei aktiviertem Indexer verwendet (siehe Abbildung 2). Beim zweiten wurde der in Abbildung 3 gezeigte Code verwendet, der den Vorabruf von Eigenschaften und das Einteilen der Dateien in Seiten durchführt. Beim dritten Versuch erfolgt auch der Vorabruf von Eigenschaften und das Einteilen der Dateien in Seiten, jedoch bei deaktiviertem Indexer. Dieser Code entspricht dem in Abbildung 3, wobei aber gemäß Anmerkung in den Kommentaren eine Zeile geändert wurde.

Abbildung 2: Naiver Code zum Aufzählen einer Bibliothek

StorageFolder folder = KnownFolders.PicturesLibrary;
QueryOptions options = new QueryOptions(
  CommonFileQuery.OrderByDate, new String[] { ".jpg", ".jpeg", ".png" });          
options.IndexerOption = IndexerOption.OnlyUseIndexer;
StorageFileQueryResult queryResult = folder.CreateFileQueryWithOptions(options);
Stopwatch watch = Stopwatch.StartNew();          
IReadOnlyList<StorageFile> files = await queryResult.GetFilesAsync();
foreach (StorageFile file in files)
{                
  IDictionary<string, object> size =
    await file.Properties.RetrievePropertiesAsync(
    new String[] { "System.Image.VerticalSize" });
  var sizeVal = size["System.Image.VerticalSize"];
}           
watch.Stop();
Debug.WriteLine("Time to run the slow way: " + watch.ElapsedMilliseconds + " ms");

Abbildung 3: Optimierter Code zum Aufzählen einer Bibliothek

StorageFolder folder = KnownFolders.PicturesLibrary;
QueryOptions options = new QueryOptions(
  CommonFileQuery.OrderByDate, new String[] { ".jpg", ".jpeg", ".png" });
// Change to DoNotUseIndexer for trial 3
options.IndexerOption = IndexerOption.OnlyUseIndexer;
options.SetPropertyPrefetch(PropertyPrefetchOptions.None, new String[] { "System.Image.VerticalSize" });
StorageFileQueryResult queryResult = folder.CreateFileQueryWithOptions(options);
Stopwatch watch = Stopwatch.StartNew();
uint index = 0, stepSize = 10;
IReadOnlyList<StorageFile> files = await queryResult.GetFilesAsync(index, stepSize);
index += 10;
// Note that I'm paging in the files as described
while (files.Count != 0)
{
  var fileTask = queryResult.GetFilesAsync(index, stepSize).AsTask();
  foreach (StorageFile file in files)
  {
// Put the value into memory to make sure that the system really fetches the property
    IDictionary<string,object> size =
      await file.Properties.RetrievePropertiesAsync(
      new String[] { "System.Image.VerticalSize" });
    var sizeVal = size["System.Image.VerticalSize"];                   
  }
  files = await fileTask;
  index += 10;
}
watch.Stop();
Debug.WriteLine("Time to run: " + watch.ElapsedMilliseconds + " ms");

Ein Blick auf die Ergebnisse mit und ohne Vorabruf genügt, um die deutlichen Leistungsunterschiede zu erkennen (siehe Abbildung 4).

Abbildung 4: Ergebnisse mit und ohne Vorabruf

Testsituation (2.600 Bilder auf einem Desktopcomputer) Durchschnittliche Laufzeit bei mehr als 10 Stichproben
Naiver Code + Indexer 9.318 ms
Alle Optimierungen + Indexer 5.796 ms
Optimierungen ohne Indexer 20.248 ms (48.420ms bei Kaltstart)

Es besteht die Möglichkeit, die Leistung des naiven Codes nahezu zu verdoppeln, indem die hier vorgestellten einfachen Optimierungen umgesetzt werden. Die Muster haben sich zudem in der Praxis bewährt. Bevor wir eine Version von Windows veröffentlichen, arbeiten wir mit den App-Teams zusammen, um sicherzustellen, dass die Fotos-App, Groove Music u. a. so gut wie möglich funktionieren. Das ist der Ursprung dieser Muster, die direkt dem Code der ersten UWP-Apps auf der UWP entnommen wurden und die Sie direkt auf Ihre Apps anwenden können.

Nachverfolgen von Änderungen im Dateisystem

Wie bereits erwähnt, ist das Aufzählen aller Dateien an einem Speicherort ein ressourcenintensiver Vorgang. Meistens sind Ihre Benutzer nicht wirklich an älteren Dateien interessiert. Sie interessieren sich für das Fotos, das sie gerade gemacht haben, den Titel, den Sie gerade heruntergeladen haben, oder das zuletzt bearbeitete Dokument. Damit die neuesten Dateien ganz oben angezeigt werden, kann Ihre App Änderungen im Dateisystem nachverfolgen und die zuletzt erstellten oder geänderten Dateien einfach finden.

Je nachdem, ob sich Ihre App im Vorder- oder Hintergrund befindet, gibt es zwei Methoden zum Nachverfolgen von Änderungen. Wenn eine App im Vordergrund ist, kann sie das "ContentsChanged"-Ereignis in einem "StorageFileQueryResult"-Objekt nutzen, um bei einer bestimmten Abfrage über Änderungen informiert zu werden. Wenn eine App im Hintergrund ist, kann sie sich für den "StorageLibraryContentChangedTrigger" registrieren, um benachrichtigt zu werden, sobald sich etwas ändert. Beides sind Kurzbenachrichtigungen zum Informieren einer App, dass sich etwas geändert hat, ohne Informationen zu den geänderten Dateien zu bieten.

Um herauszufinden, welche Dateien vor Kurzem geändert oder erstellt wurden, bietet das System die "System.Search.GatherTime"-Eigenschaft. Die Eigenschaft wird für alle Dateien an einem indizierten Speicherort festgelegt und verfolgt den letzten Zeitpunkt nach, an dem der Indexer eine Änderung der Datei bemerkt hat. Diese Eigenschaft wird allerdings anhand der Systemuhr ständig aktualisiert, sodass die üblichen Abweichungen aufgrund von Zeitzonenwechseln, Sommerzeit und manuellen Änderungen der Systemzeit hinsichtlich des Vertrauens dieses Werts in Ihrer App berücksichtigt werden sollten.

Das Registrieren für ein Änderungsverfolgungsereignis im Vordergrund ist einfach. Nachdem Sie ein "StorageFileQueryResult"-Objekt für den Umfang erstellt haben, am dem Ihre App interessiert ist, registrieren Sie sich einfach für das hier gezeigte "ContentsChanged"-Ereignis:

StorageFileQueryResult resultSet = photos.CreateFileQueryWithOptions(option);
resultSet.ContentsChanged += resultSet_ContentsChanged;

Das Ereignis wird immer dann ausgelöst, wenn sich etwas am Resultset ändert. Die App kann dann die zuletzt geänderten Dateien finden.

Das Nachverfolgen von Änderungen im Hintergrund ist etwas aufwendiger. Apps können sich für eine Benachrichtigung registrieren, wenn sich eine Datei in einer Bibliothek auf dem Gerät ändert. Komplexere Abfragen oder Umfänge werden nicht unterstützt, was heißt, dass Apps für die Aufgaben zum Sicherstellen zuständig sind, dass die Änderung etwas ist, an der sie tatsächlich interessiert ist.

Nebenbei sei angemerkt, dass die Gründe, warum Apps sich nur für die Benachrichtigungen zur Bibliotheksänderung und nicht basierend auf dem Dateityp registrieren lassen können, sind beim Design des Indexers zu suchen. Das Filtern von Abfragen basierend auf dem Speicherort einer Datei auf dem Datenträger erfolgt wesentlich schneller als das Abfragen nach einer Übereinstimmung basierend auf dem Dateityp, das bei unseren ersten Tests auf Geräten zu Leistungseinbußen geführt hat. Im weiteren Verlauf stelle ich weitere Leistungstipps vor, doch hier schon einmal ein Vorgeschmack: Das Filtern von Abfrageergebnissen anhand des Dateispeicherorts ist überaus schnell im Vergleich mit anderen Arten von Filtervorgängen.

Ich habe die Schritte für das Registrieren für eine Hintergrundaufgabe mithilfe von Codebeispielen in einem Blogbeitrag (bit.ly/1iPUVIo) erläutert, doch lassen Sie uns hier nun einige der interessanteren Schritte durchlaufen. Die erste erforderliche Aufgabe einer App ist das Erstellen des Hintergrundtriggers:

StorageLibrary library =
  await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
StorageLibraryContentChangedTrigger trigger =
  StorageLibraryContentChangedTrigger.Create(library);

Der Trigger kann auch anhand einer Sammlung von Bibliotheken erstellt werden, wenn die App am Nachverfolgen mehrerer Speicherorte interessiert ist. In diesem Fall untersucht die App nur die Bibliothek mit den Bildern, was eines der gängigsten Szenarien für Apps ist. Sie müssen sicherstellen, dass die App über die ordnungsgemäßen Fähigkeiten für den Zugriff auf die Bibliothek verfügt, die nachverfolgt werden soll. Andernfalls gibt das System die Ausnahme "Zugriff verweigert" zurück, wenn die App versucht, auf das "StorageLibrary"-Objekt zuzugreifen.

Auf Mobilgeräten mit Windows ist dies besonders interessant, da das System garantiert, dass neue Bilder des Geräts in den Speicherort der Bildbibliothek geschrieben werden. Dies erfolgt unabhängig davon, was der Benutzer auf der Einstellungsseite wählt, indem geändert wird, welche Ordner zur Bibliothek gehören.

Die App muss die Hintergrundaufgabe mithilfe von "Background­ExecutionManager" registrieren und eine Hintergrundaufgabe in die App integrieren lassen. Die Hintergrundaufgabe kann aktiviert werden, während die App im Vordergrund ist, sodass beliebiger Code potenzielle Racebedingungen beim Datei- oder Registrierungszugriff erkennen muss.

Nach erfolgter Registrierung wird Ihr App immer dann aufgerufen, wenn es eine Änderung in der Bibliothek gibt, für die sie registriert ist. Dies kann Dateien einschließen, an deren Ihre App nicht interessiert ist oder die sie nicht verarbeiten kann. In diesem Fall ist das Anwenden eines einschränkenden Filters, nachdem die Hintergrundaufgabe ausgelöst wurde, ist beste Möglichkeit zum Sicherstellen, dass keine überflüssige Hintergrundverarbeitung anfällt.

Das Auffinden der zuletzt geänderten oder hinzugefügten Dateien ist so einfach wie eine einzelne Abfrage des Indexers. Fordern Sie einfach alle Dateien mit einer Erfassungszeit an, die zum Bereich gehört, an dem die App interessiert ist. Nach Wunsch können auch hier dieselben Sortier- und Gruppierungsfunktionen verwendet werden, die für andere Abfragen verfügbar sind. Denken Sie daran, dass der Indexer intern die UTC-Zeit verwendet, weshalb Sie alle Zeitzeichenfolgen in diese Zeit umwandeln müssen, ehe Sie sie nutzen. Hier sehen Sie, wie eine Abfrage erstellt werden kann:

QueryOptions options = new QueryOptions();
DateTimeOffset lastSearchTime = DateTimeOffset.UtcNow.AddHours(-1);
// This is the conversion to Zulu time, which is used by the indexer
string timeFilter = "System.Search.GatherTime:>=" +
  lastSearchTime.ToString("yyyy\\-MM\\-dd\\THH\\:mm\\:ss\\Z")
options.ApplicationSearchFilter += timeFilter;

In diesem Fall ruft die App alle Ergebnisse in der letzten Stunde ab. In einigen Apps ist es sinnvoller, die Uhrzeit der letzten Abfrage zu speichern und diese in der Abfrage zu verwenden, doch ein beliebiger "DateTimeOffset" funktioniert. Sobald die App die Liste mit Dateien zurückerhält, kann sie diese wie zuvor erläutert aufzählen oder die Liste zum Nachverfolgen verwenden, welche Dateien neu darin sind.

Durch Kombinieren der beiden Methoden der Nachverfolgung von Änderungen mit der Erfassungszeit können Apps Änderungen im Dateisystem nachverfolgen und auf diese Änderungen auf dem Datenträger mühelos reagieren. Dies mögen relativ neue APIs in der Geschichte von Windows sein, die jedoch in der Fotos-App, Groove Music, OneDrive, Cortana sowie in Film- und Fernseh-Apps verwendet wurden, die in Windows 10 integriert sind. Sie können sie also mit der vollen Zuversicht in Ihrer App einsetzen, dass sie sich in diesen großartigen Anwendungen bereits bewährt haben.

Allgemeine bewährte Methoden

Es gibt verschiedene Dinge, die eine App, die den Indexer nutzt, berücksichtigen muss, um Fehler zu vermeiden und sicherzustellen, dass sie so schnell wie möglich arbeitet. Dazu zählen das Vermeiden unnötig komplexer Abfragen in für die Leistung kritischen Teilen der App, das ordnungsgemäße Verwenden von Eigenschaftsenumerationen und Erkennen von Indizierungsverzögerungen.

Der Entwurf einer Abfrage hat einen entscheidenden Einfluss auf ihre Leistung. Wenn der Indexer seine unterstützende Datenbank abfragt, erfolgen einige Abfragen schneller, was an der Anordnung der Informationen auf dem Datenträger liegt. Eine auf dem Dateispeicherort basierende Filterung ist stets schnell, da der Indexer rasch umfangreiche Teile des Indexes aus der Abfrage entfernen kann. Dies spart Prozessor- und E/A-Zeit, da es weniger Zeichenfolgen gibt, die abgerufen und verglichen werden müssen, während nach Übereinstimmungen für die Abfragebegriffe gesucht wird.

Der Indexer ist leistungsstark genug für die Verarbeitung regulärer Ausdrücke, doch einige Formen sind berüchtigt für ihren bremsenden Charakter. Das Schlimmste, was in einen Indexerabfrage einbezogen werden kann, ist eine Suffixsuche. Dies ist eine Abfrage nach Begriffen, die mit einem bestimmten Wert enden. Nehmen wir als Beispiel die Abfrage "*tion", die nach allen Dokumenten mit Worten sucht, die auf "tion" enden. Da der Index anhand des ersten Buchstabens jedes Tokens sortiert ist, gibt es keine schnelle Möglichkeit, dieser Abfrage entsprechende Begriffe zu finden. Jedes Token muss im gesamten Index decodiert und mit dem Suchbegriff verglichen werden, was überaus langsam ist.

Enumerationen können Abfragen beschleunigen, jedoch in internationalen Builds ein unerwartetes Verhalten aufweisen. Jeder, der schon einmal ein Suchsystem entwickelt hat, weiß, wie viel schneller auf einer Enumeration basierende Vergleiche gegenüber Zeichenfolgenvergleichen sind. Das gilt auch für den Indexer. Um Ihrer App die Arbeit zu erleichtern, bietet das Eigenschaftssystem verschiedene Enumerationen zum Filtern von Ergebnissen zu weniger Elementen, ehe aufwendige Zeichenfolgenvergleiche erfolgen. Ein gängiges Beispiel hierfür ist das Verwenden des "System.Kind"-Filters zum Einschränken der Ergebnisse auf nur die Dateiarten, die die App verarbeiten kann, wie z. B. Musikdateien oder Dokumente.

Es gibt einen häufigen Fehler, den alle Nutzer von Enumerationen kennen müssen. Wenn ein Benutzer nur nach Musikdateien sucht, funktioniert in einer EN-US-Version von Windows das Hinzufügen von "System.Kind:=music" zur Abfrage reibungslos, um die Suchergebnisse einzuschränken und eine Abfrage zu beschleunigen. Dies funktioniert ggf. auch in einigen anderen Sprachen, möglicherweise gerade gut genug, um Tests der Internationalisierung zu bestehen. Es funktioniert allerdings nicht, wenn das System "music" nicht als einen englischen Begriff erkennen kann und es stattdessen in die lokale Sprache analysiert.

Der ordnungsgemäße Weg zum Verwenden einer Enumeration wie "System.Kind" ist die klare Festlegung, dass die App beabsichtigt, den Wert als Enumeration und nicht als Suchbegriff zu verwenden. Dazu dient die "enumeration#value"-Syntax. Um beispielsweise ordnungsgemäß ausschließlich Musikergebnisse zu filtern, muss der Code "System.Kind:=System.Kind#Music" enthalten. Dies funktioniert in allen von Windows unterstützten Sprachen und filtert die Ergebnisse auf nur die Dateien, die das System als Musikdateien erkennt.

Eine ordnungsgemäße Escapesequenz für Advanced Query Syntax (AQS) kann helfen sicherzustellen, dass Ihre Benutzer keine allzu großen Schwierigkeiten beim Reproduzieren von Abfrageproblemen haben. AQS bietet eine Vielzahl von Features, die Benutzern das Hinzufügen von Anführungszeichen oder Klammern erlauben, um die Verarbeitung der Abfrage zu beeinflussen. Dies bedeutet, dass Apps Abfragebegriffe in Escapezeichen setzen müssen, die ggf. diese Zeichen enthalten. Eine Suche nach "Document(8).docx" führt zu einem Analysefehler und zur Rückgabe falscher Ergebnisse. Stattdessen muss die App den Suchbegriff wie folgt mit Escapezeichen versehen: Document%288%29.docx. Hiermit werden Elemente im Index zurückgegeben, die dem Suchbegriff entsprechen, anstatt das System zum Versuch zu veranlassen, die Klammer als Teil der Abfrage zu analysieren.

Eine überaus umfassende Erörterung der gesamten Features von AQS und wie Sie dafür sorgen können, dass Ihre Abfragen ordnungsgemäß sind, finden Sie in der Dokumentation unter bit.ly/1Fhacfl. Hier finden Sie viele nützliche Informationen und weitere Details zu den hier erwähnten Tipps.

Ein Hinweis zu Indizierungsverzögerungen: Die Indizierung erfolgt nicht augenblicklich, was heißt, dass Elemente, die im Index oder in Benachrichtigungen basierend auf dem Indexer angezeigt werden, vom Zeitpunkt des Schreibens der Datei zeitlich verzögert sind. Bei normaler Systemlast bewegt sich die Verzögerung in einer Größenordnung von 100 ms, was schneller ist, als die meisten Apps das Dateisystem abfragen können, und deshalb unbemerkt bleibt. Es gibt Fälle, bei denen ein Benutzer Tausende von Dateien auf seinem Computer verschiebt und der Indexer erkennbar hinterherhinkt.

In diesen Fällen werden zwei Dinge empfohlen, die Apps tun sollten: Erstens muss die App eine Abfrage für die Speicherorte im Dateisystem offen halten, an denen die App am meisten interessiert ist. Dies erfolgt meist durch Erstellen eines "StorageFileQueryResult"-Objekts für die Speicherorte im Dateisystem, die die App durchsuchen soll. Wenn der Indexer erkennt, dass eine App eine offene Abfrage aufweist, priorisiert er die Indizierung dieser Bereiche im Vergleich mit allen anderen Bereichen. Führen Sie diesen Schritt jedoch nicht für einen größeren Bereich als nötig aus. Der Indexer stellt das Beachten von Benachrichtigungen zu Systembackoffs und aktiven Benutzern ein, um die Änderungen so schnell wie möglich zu verarbeiten, weshalb Benutzer ggf. eine Beeinträchtigung der Systemleistung bemerken, während dies passiert.

Die andere Empfehlung ist, die Benutzer zu warnen, dass das System den Anschluss an die Dateivorgänge findet. Einige Apps wie Cortana zeigen oben auf der Benutzeroberfläche eine Meldung, während andere die Ausführung komplexer Abfragen beenden und eine einfache Version der Umgebung zeigen. Es liegt an Ihnen zu bestimmen, was das Beste für Ihre App-Erfahrung ist.

Zusammenfassung

Dies war eine Schnelleinführung in die Features, die Nutzern des Indexers und von Windows Storage-APIs unter Windows 10 zur Verfügung stehen. Weitere Informationen zum Verwenden von Abfragen zum Übergeben von Kontext bei der App-Aktivierung oder Codebeispiele für die Hintergrundtrigger finden Sie im Blog unseres Teams unter bit.ly/1iPUVIo. Wir arbeiten ständig mit Entwicklern zusammen, um dafür zu sorgen, dass die Such-APIs komfortabel zu nutzen sind. Wir freuen uns über Ihr Feedback dazu, was alles geht, und dazu, was Sie sich auf der Oberfläche noch alles wünschen.


Adam Wilsonist ein Programm-Manager im Windows Developer Ecosystem and Platform-Team. Seine Hauptgebiete sind der Windows-Indexer und die Zuverlässigkeit von Pushbenachrichtigungen. Zuvor hat er an Speicher-APIs für Windows Phone 8.1 gearbeitet. Sie erreichen Ihn unter adwilso@microsoft.com.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Sami Khoury
Sami Khoury ist Entwickler im Windows Developer Ecosystem and Platform-Team. Er leitet die Entwicklung des Windows-Indexers.