Aggiornamento marzo 2020
.NET Core 3.0 (e 3.1) sono ora disponibili, con supporto completo per flussi asincroni. Microsoft.Bcl.AsyncInterfaces aggiunge il supporto per loro a .NET Standard 2.0 e .NET Framework 4.6.1+, anche se 4.7.2 dovrebbe essere usato per motivi di sanità mentale. Come spiegano i documenti sul supporto per l'implementazione di .NET Standard
Risposta originale
Se controlli il codice sorgente, vedrai che il tuo sospetto è quasi corretto. Quando buffered
è falso, QueryAsync
trasmetterà in streaming in modo sincrono .
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;
}
Come spiega il commento, non è possibile utilizzare ReadAsync
quando il tipo restituito dovrebbe essere IEnumerable. Ecco perché è stato necessario introdurre gli enumerabili asincroni di C# 8.
Il codice per ExecuteReaderSync è:
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();
}
}
Usa Read
invece di ReadAsync
.
I flussi asincroni C#8 consentiranno di riscriverlo per restituire un IAsyncEnumerable
. Cambiare semplicemente la versione della lingua non risolverà il problema.
Dati i documenti attuali sui flussi asincroni, potrebbe essere simile a:
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();
}
}
Buongiorno i flussi asincroni sono una delle cose che possono funzionare solo su .NET Core e probabilmente non sono ancora implementate. Quando ho provato a scriverne uno in Sharplab.io, Kaboom. [connection lost, reconnecting…]
Nel contesto di dapper in particolare , sì:necessita di un'API diversa come spiegato dall'eccellente risposta di @Panagiotis. Quella che segue non è una risposta in quanto tale, ma è un contesto aggiuntivo che gli implementatori che devono affrontare le stesse sfide potrebbero voler considerare.
Non l'ho ancora "aggiustato" per dapper (anche se ho per SE.Redis), e sono combattuto tra varie opzioni:
- aggiungi una nuova API per .NET Core solo , restituendo un tipo enumerabile asincrono appropriato
- distruggi completamente l'API esistente come modifica sostanziale (una "importante" ecc.), modificandola per restituire un tipo enumerabile asincrono
Probabilmente andremo con "1", ma devo dire che la seconda opzione è insolitamente allettante, per buoni motivi:
- L'API esistente probabilmente non fa ciò che la gente si aspetta che faccia
- vorremmo che il nuovo codice iniziasse a usarlo
Ma la cosa strana è la .NET Core 3.0 di IAsyncEnumerable<T>
- poiché ovviamente Dapper non ha come target solo .NET Core 3.0; potremmo:
- limita la funzionalità a .NET Core 3.0 e restituisce
IAsyncEnumerable<T>
- limita la libreria a .NET Core 3.0 e restituisci
IAsyncEnumerable<T>
- prendere una dipendenza da System.Linq.Async (che non è "ufficiale", ma è abbastanza ufficiale per i nostri scopi) per i framework precedenti e restituire
IAsyncEnumerable<T>
- restituisce un tipo enumerabile personalizzato che non è in realtà
IAsyncEnumerable<T>
(ma che implementaIAsyncEnumerable<T>
quando disponibile) e implementare manualmente la macchina a stati, la natura tipo anatra diforeach
significa che funzionerà bene purché il nostro tipo enumerabile personalizzato fornisca i metodi giusti
Penso che probabilmente vai con l'opzione 3, ma per ribadire:sì, qualcosa deve cambiare.
(Questo dovrebbe essere un commento // non abbastanza reputazione, finora )
Marc Gravell menziona nella sua risposta che IAsyncEnumerable<T>
sarebbe preferibile, ma a causa della dipendenza da NET Core 3.0, potrebbe essere meglio prendere una dipendenza da System.Linq.Async
(che potrebbe essere considerato "abbastanza ufficiale")...
In questo contesto, mi è venuto in mente https://github.com/Dasync/AsyncEnumerable (licenza MIT):mira ad aiutare
Ancora una frase, RE:"Cosa succede quando viene rilasciato C# 8.0?" (FAQ)