Eine der wichtigsten Funktionen von .NET Core 3.0 und C # 8.0 ist die neue Funktion
IAsyncEnumerable<T>(auch bekannt als asynchroner Thread). Aber was ist das Besondere an ihm? Was können wir jetzt tun, was vorher unmöglich war?
In diesem Artikel werden wir untersuchen, welche Aufgaben es
IAsyncEnumerable<T>lösen soll, wie es in unseren eigenen Anwendungen implementiert wird und warum IAsyncEnumerable<T>es in vielen Situationen ersetzt wird.
Testen Sie alle neuen Funktionen in .NET Core 3Task<IEnumerable<T>>
Leben vor IAsyncEnumerable<T>
IAsyncEnumerable<T>Vielleicht ist der beste Weg zu erklären, warum
IAsyncEnumerable<T>es so nützlich ist, die Probleme zu betrachten, mit denen wir zuvor konfrontiert waren.
Stellen Sie sich vor, wir erstellen eine Bibliothek für die Interaktion mit Daten und benötigen eine Methode, die einige Daten von einem Geschäft oder einer API anfordert. Normalerweise gibt diese Methode Folgendes zurück :
Task<IEnumerable<T>>
public async Task<IEnumerable<Product>> GetAllProducts()
Um diese Methode zu implementieren, fordern wir Daten normalerweise asynchron an und geben sie zurück, wenn sie abgeschlossen sind. Das Problem wird offensichtlicher, wenn wir mehrere asynchrone Aufrufe durchführen müssen, um Daten abzurufen. Beispielsweise kann unsere Datenbank oder API Daten auf ganzen Seiten zurückgeben, wie bei dieser Implementierung mit Azure Cosmos DB:
public async Task<IEnumerable<Product>> GetAllProducts()
{
Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
var products = new List<Product>();
while (iterator.HasMoreResults)
{
foreach (var product in await iterator.ReadNextAsync())
{
products.Add(product);
}
}
return products;
}
Beachten Sie, dass wir alle Ergebnisse in einer while-Schleife durchlaufen, Produktobjekte instanziieren, in eine Liste einfügen und schließlich das Ganze zurückgeben. Dies ist insbesondere bei großen Datenmengen recht ineffizient.
Vielleicht können wir eine effizientere Implementierung erstellen, indem wir unsere Methode so ändern, dass die Ergebnisse jeweils auf einer ganzen Seite zurückgegeben werden:
public IEnumerable<Task<IEnumerable<Product>>> GetAllProducts()
{
Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
while (iterator.HasMoreResults)
{
yield return iterator.ReadNextAsync().ContinueWith(t =>
{
return (IEnumerable<Product>)t.Result;
});
}
}
Der Aufrufer verwendet die folgende Methode:
foreach (var productsTask in productsRepository.GetAllProducts())
{
foreach (var product in await productsTask)
{
Console.WriteLine(product.Name);
}
}
Diese Implementierung ist effizienter, aber die Methode kehrt jetzt zurück . Wie wir aus dem aufrufenden Code ersehen können, ist das Aufrufen der Methode und das Verarbeiten der Daten nicht intuitiv. Noch wichtiger ist, dass Paging ein Implementierungsdetail einer Datenzugriffsmethode ist, über die der Anrufer nichts wissen muss.
IEnumerable<Task<IEnumerable<Product>>>
IAsyncEnumerable<T> beeilen sich zu helfen
IAsyncEnumerable<T>Wir möchten wirklich asynchron Daten aus unserer Datenbank abrufen und die Ergebnisse beim Empfang an den Anrufer zurückgeben.
Im synchronen Code kann eine Methode, die IEnumerable zurückgibt, eine Yield-Return-Anweisung verwenden, um jedes Datenelement aus der Datenbank an den Aufrufer zurückzugeben.
public IEnumerable<Product> GetAllProducts()
{
Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
while (iterator.HasMoreResults)
{
foreach (var product in iterator.ReadNextAsync().Result)
{
yield return product;
}
}
}
Jedoch nie tun THIS ! Der obige Code verwandelt einen asynchronen Datenbankaufruf in einen blockierenden Aufruf und skaliert nicht.
Wenn wir nur
yield returnasynchrone Methoden verwenden könnten ! Es war unmöglich ... bis jetzt.
IAsyncEnumerable<T>wurde in .NET Core 3 (.NET Standard 2.1) eingeführt. Es bietet einen Enumerator mit einer Methode MoveNextAsync(), die erwartet werden kann. Dies bedeutet, dass der Initiator asynchrone Anrufe tätigen kann, während er (in der Mitte) die Ergebnisse empfängt.
Anstatt Task
<IEnumerable <T >> zurückzugeben, kann unsere Methode jetzt return IAsyncEnumerable<T>und return Yield zurückgeben, um Daten zu übergeben.
public async IAsyncEnumerable<Product> GetAllProducts()
{
Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
while (iterator.HasMoreResults)
{
foreach (var product in await iterator.ReadNextAsync())
{
yield return product;
}
}
}
Um die Ergebnisse zu verwenden, müssen wir die neue
await foreach()in C # 8 verfügbare Syntax verwenden :
await foreach (var product in productsRepository.GetAllProducts())
{
Console.WriteLine(product);
}
Das ist viel schöner. Die Methode erzeugt Daten, sobald sie eingehen. Der aufrufende Code verwendet die Daten in seinem eigenen Tempo.
IAsyncEnumerable<T> und ASP.NET Core
IAsyncEnumerable<T>Ab .NET Core 3 Preview 7 kann ASP.NET eine IAsyncEnumerable von einer API-Controller-Aktion zurückgeben. Dies bedeutet, dass wir die Ergebnisse unserer Methode direkt zurückgeben können und Daten aus der Datenbank effizient an eine HTTP-Antwort übergeben können.
[HttpGet]
public IAsyncEnumerable<Product> Get()
=> productsRepository.GetAllProducts();
Ersatz fürTask<IEnumerable<T>>IAsyncEnumerable<T>
Task<IEnumerable<T>>IAsyncEnumerable<T>Mit der Zeit werden wir uns mit .NET Core 3 und .NET Standard 2.1 vertraut machen. Es wird erwartet,
IAsyncEnumerable<T>dass es an Orten verwendet wird, an denen wir normalerweise Task <IEnumerable> verwenden würden.
Ich freue mich auf Unterstützung
IAsyncEnumerable<T>in den Bibliotheken. In diesem Artikel wurde ein ähnlicher Code zum Abfragen von Daten mit dem Azure Cosmos DB 3.0 SDK angezeigt:
var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
while (iterator.HasMoreResults)
{
foreach (var product in await iterator.ReadNextAsync())
{
Console.WriteLine(product.Name);
}
}
Wie in unseren vorherigen Beispielen lädt uns das native Cosmos DB SDK auch mit den Details der Paging-Implementierung, was die Verarbeitung von Abfrageergebnissen erschwert.
Um zu sehen, wie es aussehen könnte, wenn es
GetItemQueryIterator<Product>()stattdessen zurückgegeben wird IAsyncEnumerable<T>, können wir eine Erweiterungsmethode erstellen in FeedIterator:
public static class FeedIteratorExtensions
{
public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this FeedIterator<T> iterator)
{
while (iterator.HasMoreResults)
{
foreach(var item in await iterator.ReadNextAsync())
{
yield return item;
}
}
}
}
Wir können jetzt die Ergebnisse unserer Abfragen viel besser behandeln:
var products = container
.GetItemQueryIterator<Product>("SELECT * FROM c")
.ToAsyncEnumerable();
await foreach (var product in products)
{
Console.WriteLine(product.Name);
}
Zusammenfassung
IAsyncEnumerable<T>- ist eine willkommene Ergänzung zu .NET und macht Ihren Code in vielen Fällen schöner und effizienter. Weitere Informationen hierzu finden Sie in den folgenden Ressourcen:
- Tutorial: Generieren und verwenden Sie asynchrone Streams mit C # 8.0 und .NET Core 3.0
- Vorschläge für C # -Sprachen - Async-Streams
Zustandsentwurfsmuster