El siguiente método no compila. ¿Alternativas?
public static async Task<IEnumerable<object[]>> GetRecordsAsync( this Transaction transaction, string commandText, params SqlParameter[] parameters) { // Get a SqlDataReader var reader = await transaction.GetReaderAsync(commandText, parameters); var fieldCount = -1; // Begin iterating through records asynchronously while (await reader.ReadAsync()) // Note we don't loop until .ReadAsync returns a boolean { // Grab all the field values out if (fieldCount < 0) fieldCount = reader.FieldCount; var fields = new object[fieldCount]; reader.GetValues(fields); // Yield return the field values from this record yield return fields; } }Mensaje de error:
El cuerpo de 'TransactionExtensions.GetRecordsAsync(Transaction, string, params SqlParameter[])' no puede ser un bloque de iterador porque 'Task>' no es un tipo de interfaz de iterador
No veo una manera de adaptar esta respuesta a una pregunta que suena similar (pero diferente), porque no sé a priori cuántas veces irá el bucle.
Editar: formato fijo
Basado en el comentario de @SLaks a la pregunta, aquí hay una alternativa general usando Extensiones reactivas :
/// <summary> /// Turns the given asynchronous functions into an IObservable /// </summary> static IObservable<T> ToObservable<T>( Func<Task<bool>> shouldLoopAsync, Func<Task<T>> getAsync) { return Observable.Create<T>( observer => Task.Run(async () => { while (await shouldLoopAsync()) { var value = await getAsync(); observer.OnNext(value); } observer.OnCompleted(); } ) ); }Ejemplo de uso, adaptado para resolver el caso específico de la pregunta:
/// <summary> /// Asynchronously processes each record of the given reader using the given handler /// </summary> static async Task ProcessResultsAsync(this SqlDataReader reader, Action<object[]> fieldsHandler) { // Set up async functions for the reader var shouldLoopAsync = (Func<Task<bool>>)reader.ReadAsync; var getAsync = new Func<SqlDataReader, Func<Task<object[]>>>(_reader => { var fieldCount = -1; return () => Task.Run(() => { Interlocked.CompareExchange(ref fieldCount, _reader.FieldCount, -1); var fields = new object[fieldCount]; _reader.GetValues(fields); return fields; }); })(reader); // Turn the async functions into an IObservable var observable = ToObservable(shouldLoopAsync, getAsync); // Process the fields as they become available var finished = new ManualResetEventSlim(); // This will be our signal for when the observable completes using (observable.Subscribe( onNext: fieldsHandler, // Invoke the handler for each set of fields onCompleted: finished.Set // Set the gate when the observable completes )) // Don't forget best practice of disposing IDisposables // Asynchronously wait for the gate to be set await Task.Run((Action)finished.Wait); } (Tenga en cuenta que getAsync podría simplificarse en el bloque de código anterior, pero me gusta lo explícito que es sobre el cierre que se está creando)
...y finalmente:
// Get a SqlDataReader var reader = await transaction.GetReaderAsync(commandText, parameters); // Do something with the records await reader.ProcessResultsAsync(fields => { /* Code here to process each record */ });No devuelva Task<IEnumerable<T>> y ni siquiera use Task para esto; en su lugar, devuelva un IAsyncEnumerable<T> . No se necesitan bibliotecas de terceros u otras soluciones alternativas, ni siquiera es necesario alterar el cuerpo de su método original.
public static async IAsyncEnumerable<object[]> GetRecordsAsync( this Transaction transaction, string commandText, params SqlParameter[] parameters) { // Get a SqlDataReader var reader = await transaction.GetReaderAsync(commandText, parameters); var fieldCount = -1; // Begin iterating through records asynchronously while (await reader.ReadAsync()) // Note we don't loop until .ReadAsync returns a boolean { // Grab all the field values out if (fieldCount < 0) fieldCount = reader.FieldCount; var fields = new object[fieldCount]; reader.GetValues(fields); // Yield return the field values from this record yield return fields; } }Lo resolví sin extensiones de terceros:
public async Task<IEnumerable<Item>> GetAllFromDb() { OracleConnection connection = null; DbDataReader reader = null; try { connection = new OracleConnection(connectionString); var command = new OracleCommand(queryString, connection); connection.Open(); reader = await command.ExecuteReaderAsync(); return this.BuildEnumerable(connection, reader); } catch (Exception) { reader?.Dispose(); connection?.Dispose(); throw; } } private IEnumerable<Item> BuildEnumerable(OracleConnection connection, DbDataReader reader) { using (connection) using (reader) { while (reader.Read()) { var item = new Item() { Prop = reader.GetString(0), }; yield return item; } } } Este ejemplo es para Oracle Data Reader pero el mismo enfoque es aplicable a cualquier operación asíncrona combinada con yield return