Was passiert mit der Rückgabe von IEnumerable bei Verwendung mit async/await (Streaming von Daten von SQL Server mit Dapper)?

Was passiert mit der Rückgabe von IEnumerable bei Verwendung mit async/await (Streaming von Daten von SQL Server mit Dapper)?

Aktualisierung März 2020

.NET Core 3.0 (und 3.1) sind jetzt herausgekommen, mit voller Unterstützung für asynchrone Streams. Die Microsoft.Bcl.AsyncInterfaces fügen Unterstützung für sie zu .NET Standard 2.0 und .NET Framework 4.6.1+ hinzu, obwohl 4.7.2 aus Gründen der Gesundheit verwendet werden sollte. Wie die Dokumentation zur .NET Standard-Implementierungsunterstützung erklärt

Originalantwort

Wenn Sie den Quellcode überprüfen, werden Sie sehen, dass Ihre Vermutung fast richtig ist. Wenn buffered ist falsch, QueryAsync wird synchron gestreamt .

if (command.Buffered)
{
    var buffer = new List<T>();
    var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
    while (await reader.ReadAsync(cancel).ConfigureAwait(false))
    {
        object val = func(reader);
        if (val == null || val is T)
        {
            buffer.Add((T)val);
        }
        else
        {
            buffer.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture));
        }
    }
    while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ }
    command.OnCompleted();
    return buffer;
}
else
{
    // can't use ReadAsync / cancellation; but this will have to do
    wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior
    var deferred = ExecuteReaderSync<T>(reader, func, command.Parameters);
    reader = null; // to prevent it being disposed before the caller gets to see it
    return deferred;
}

Wie der Kommentar erklärt, ist es nicht möglich, ReadAsync zu verwenden wenn der Rückgabetyp IEnumerable sein soll. Aus diesem Grund mussten die asynchronen Enumerables von C# 8 eingeführt werden.

Der Code für ExecuteReaderSync lautet:

private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
{
    using (reader)
    {
        while (reader.Read())
        {
            yield return (T)func(reader);
        }
        while (reader.NextResult()) { /* ignore subsequent result sets */ }
        (parameters as IParameterCallbacks)?.OnCompleted();
    }
}

Es verwendet Read statt ReadAsync .

Asynchrone C#8-Streams ermöglichen das Umschreiben, um einen IAsyncEnumerable zurückzugeben . Einfach die Sprachversion zu ändern wird das Problem nicht lösen.

Angesichts der aktuellen Dokumentation zu asynchronen Streams könnte dies folgendermaßen aussehen:

private static async IAsyncEnumerable<T> ExecuteReaderASync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
{
    using (reader)
    {
        while (await reader.ReadAsync())
        {
            yield return (T)func(reader);
        }

        while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ }
         command.OnCompleted();
        (parameters as IParameterCallbacks)?.OnCompleted();
    }
}

Buuuuut Asynchrone Streams sind eines der Dinge, die nur auf .NET Core funktionieren können und wahrscheinlich noch nicht implementiert sind. Als ich versuchte, einen in Sharplab.io zu schreiben, war Kaboom. [connection lost, reconnecting…]


Im Kontext von dapper speziell , ja:Es benötigt eine andere API, wie in der hervorragenden Antwort von @Panagiotis erläutert. Was folgt, ist keine Antwort als solches, ist aber ein zusätzlicher Kontext, den Implementierer, die vor denselben Herausforderungen stehen, berücksichtigen möchten.

Ich habe das noch nicht für dapper "angereichert" (obwohl ich es habe für SE.Redis), und ich bin zwischen verschiedenen Optionen hin- und hergerissen:

  1. fügen Sie nur eine neue API für .NET Core hinzu , der einen geeigneten asynchron aufzählbaren Typ zurückgibt
  2. zerstören Sie die vorhandene API vollständig als Breaking Change (eine "große" usw.) und ändern Sie sie so, dass sie einen asynchron aufzählbaren Typ zurückgibt

Wir werden wahrscheinlich "1" nehmen, aber ich muss sagen, die zweite Option ist aus guten Gründen ungewöhnlich verlockend:

  • Die vorhandene API macht wahrscheinlich nicht das, was die Leute von ihr erwarten
  • Wir möchten, dass neuer Code damit beginnt, ihn zu verwenden

Aber das Merkwürdige ist die .NET Core 3.0-Nähe von IAsyncEnumerable<T> - da Dapper offensichtlich nicht nur auf .NET Core 3.0 abzielt; wir könnten:

  1. Beschränken Sie die Funktion auf .NET Core 3.0 und geben Sie IAsyncEnumerable<T> zurück
  2. Beschränken Sie die Bibliothek zu .NET Core 3.0 und geben Sie IAsyncEnumerable<T> zurück
  3. Nehmen Sie eine Abhängigkeit von System.Linq.Async (was nicht "offiziell", aber für unsere Zwecke offiziell genug ist) für die vorherigen Frameworks und geben Sie IAsyncEnumerable<T> zurück
  4. gibt einen benutzerdefinierten Aufzählungstyp zurück, der nicht ist eigentlich IAsyncEnumerable<T> (aber die IAsyncEnumerable<T> implementiert wenn verfügbar) und die Zustandsmaschine manuell implementieren - die entenartige Natur von foreach bedeutet, dass dies gut funktionieren wird, solange unser benutzerdefinierter aufzählbarer Typ die richtigen Methoden bereitstellt

Ich denke, das werden wir wahrscheinlich Wählen Sie Option 3, aber um es noch einmal zu wiederholen:Ja, es muss sich etwas ändern.


(Dies soll ein Kommentar sein // noch nicht genug Reputation )

Marc Gravell erwähnt in seiner Antwort, dass IAsyncEnumerable<T> wäre vorzuziehen, aber aufgrund der Abhängigkeit von NET Core 3.0 könnte es besser sein, eine Abhängigkeit von System.Linq.Async zu nehmen (was als "offiziell genug" angesehen werden könnte)...

In diesem Zusammenhang kam mir https://github.com/Dasync/AsyncEnumerable in den Sinn (MIT-Lizenz):Es soll helfen

Noch ein Zitat, RE:"Was passiert, wenn C# 8.0 veröffentlicht wird?" (Häufig gestellte Fragen)