¿Cómo comprimir de forma simultánea dos IAsyncEnumerables con Zip?
Tengo dos secuencias asíncronas que quiero “unir” en pares, y para ello utilicé el operador Zip
del paquete System.Linq.Async. Sin embargo, este operador se comporta de manera indeseable para mi caso. En lugar de enumerar las dos secuencias de forma concurrente, las enumera de forma secuencial, lo que resulta en que se suma la latencia. Cada una de mis secuencias emite un elemento aproximadamente cada segundo, y esperaba que la secuencia combinada emitiera también pares unidos cada segundo, pero en realidad obtengo un par cada 2 segundos. A continuación se muestra un ejemplo mínimo que demuestra este comportamiento:
static async IAsyncEnumerable<int> Primera()
{
for (int i = 1; i <= 5; i++) { await Task.Delay(1000); yield return i; }
}
static async IAsyncEnumerable<int> Segunda()
{
for (int i = 1; i <= 5; i++) { await Task.Delay(1000); yield return i; }
}
var cronómetro = Stopwatch.StartNew();
await foreach (var par in Primera().Zip(Segunda()))
Console.WriteLine(par);
Console.WriteLine($"Duración: {cronómetro.ElapsedMilliseconds:#,0} ms");
Salida:
ninguna
(1, 1)
(2, 2)
(3, 3)
(4, 4)
(5, 5)
Duración: 10,155 ms
¿Hay alguna forma de “unir” estas dos secuencias utilizando Zip
de manera que el programa se complete en 5 segundos en lugar de 10? Estoy interesado en un operador personalizado o en una combinación de operadores de los paquetes oficiales que tenga el comportamiento deseado.
davy.ai
El operador
Zip
del paqueteSystem.Linq.Async
enumera las dos secuencias de forma secuencial, lo cual añade latencia. Una posible solución es utilizar el operadorMerge
del mismo paquete, junto conCombineLatest
del paqueteSystem.Reactive.Linq
.El operador
Merge
nos permite combinar dos secuencias de forma concurrente, mientras queCombineLatest
nos permite combinar los últimos elementos de cada secuencia en un par. Aquí tienes un ejemplo de implementación:En esta implementación, creamos dos enumeradores asíncronos y utilizamos el método
Task.WhenAny
para esperar el siguiente elemento de cada secuencia de forma concurrente. Una vez que tenemos ambos elementos, generamos un par y seguimos esperando los siguientes elementos.Así, podemos obtener el comportamiento deseado, con la siguiente salida:
</tfirst></tfirst,>”>Pruébalo en Fiddle.