¿Cómo consigo que las canalizaciones asíncronas que constituyen el marco de trabajo de Combine se alineen sincrónicamente (en serie)?
Supongamos que tengo 50 URL de las que quiero descargar los recursos correspondientes, y digamos que quiero hacerlo de uno en uno. Sé cómo hacerlo con Operation / OperationQueue, por ejemplo, utilizando una subclase de Operation que no se declara terminada hasta que se completa la descarga. ¿Cómo haría lo mismo con Combine?
Por el momento, todo lo que se me ocurre es mantener una lista global de las URL restantes y abrir una, configurar esa tubería para una descarga, hacer la descarga y, en sink
la tubería, repetir. Eso no parece muy combinado.
Intenté hacer una matriz de las URL y asignarla a una variedad de editores. Sé que puedo "producir" un editor y hacer que se publique en el futuro utilizando flatMap
. Pero aún sigo haciendo todas las descargas simultáneamente. No hay ninguna forma combinada de recorrer la matriz de manera controlada, ¿o sí?
(También imaginé hacer algo con Future, pero me confundí irremediablemente. No estoy acostumbrado a esta forma de pensar).
append
es exactamente lo que estaba buscando - Su código se puede ajustar considerablemente; en particular, no hay necesidad de regresar prematuramente en el caso dondecount == 1
, porque en ese casodropFirst
estará vacío y simplemente no haremos un bucle. Y no hay necesidad de mantener laoutput
variable, porque podemos usarla enreduce
lugar defor...in
. Vea mi respuesta para una representación más ajustada.Puede crear un Suscriptor personalizado donde reciba Subscribers.Demand.max (1). En ese caso, el suscriptor solicitará el siguiente valor solo cuando reciba uno. El ejemplo es para Int.publisher, pero un retraso aleatorio en el mapa imita el tráfico de red :-)
Impresión del patio ...
ACTUALIZACIÓN finalmente encontré
.flatMap(maxPublishers: )
, lo que me obliga a actualizar este interesante tema con un enfoque un poco diferente. Por favor, vea que estoy usando la cola global para la programación, no solo un retraso aleatorio, solo para asegurarme de que recibir una transmisión serializada no sea un comportamiento "aleatorio" o "afortunado" :-)huellas dactilares
Basado en escrito aquí
.publicar por fascículos()?
definido por Clay Ellis la respuesta aceptada podría ser reemplazada por
.publisher.flatMap (maxPublishers: .max (1)) {$ 0}
mientras que la versión "no serializada" debe usar
.publisher.flatMap {$ 0}
"ejemplo del mundo real"
huellas dactilares
Me parece muy útil en otros escenarios también. Intente utilizar el valor predeterminado de maxPublishers en el siguiente fragmento y compare los resultados :-)
fuente
maxPublishers
parámetro, podemos agregar contrapresión. Esto va con lo que dije en mi pregunta: "Sé que puedo" producir "un editor y hacer que se publique en el futuro usando flatMap. Pero aún así estoy haciendo todas las descargas simultáneamente". Pues bien, con elmaxPublishers
parámetro, que son no simultánea.En todos los demás marcos reactivos esto es realmente fácil; solo se usa
concat
para concatenar y aplanar los resultados en un solo paso y luego se pueden obtenerreduce
los resultados en una matriz final. Apple hace esto difícil porquePublisher.Concatenate
no tiene sobrecarga que acepte una variedad de editores. Hay rarezas similares conPublisher.Merge
. Tengo la sensación de que esto tiene que ver con el hecho de que devuelven editores genéricos anidados en lugar de devolver un solo tipo genérico como rx Observable. Supongo que puedes llamar a Concatenateen un bucle y luego reducir los resultados concatenados en una sola matriz, pero realmente espero que aborden este problema en la próxima versión. Ciertamente, es necesario concatenar a más de 2 publicadores y fusionar más de 4 publicadores (y las sobrecargas para estos dos operadores ni siquiera son consistentes, lo cual es extraño).EDITAR:
Volví a esto y descubrí que de hecho puedes concatenar una variedad arbitraria de editores y emitirán en secuencia. No tengo idea de por qué no hay una función como
ConcatenateMany
hacer esto por usted, pero parece que siempre y cuando esté dispuesto a usar un editor borrado no es tan difícil escribir uno. Este ejemplo muestra que la fusión emite en orden temporal mientras que concat emite en el orden de combinación:fuente
concat
serializar (en los otros marcos reactivos)?.append
es un operador que crea unPublisher.Concatenate
.De la pregunta original:
Aquí hay un ejemplo de juguete para reemplazar el problema real:
Esto emite los enteros del 1 al 10 en orden aleatorio llegando a tiempos aleatorios. El objetivo es hacer algo con
collection
eso hará que emita los enteros del 1 al 10 en orden.Ahora vamos a cambiar solo una cosa: en la línea
agregamos el
maxPublishers
parámetro:Presto, ahora hacer emiten los números enteros de 1 a 10, en orden, con intervalos aleatorios entre ellos.
Apliquemos esto al problema original. Para demostrarlo, necesito una conexión a Internet bastante lenta y un recurso bastante grande para descargar. Primero, lo haré con ordinario
.flatMap
:El resultado es
lo que muestra que estamos haciendo las tres descargas simultáneamente. Ok, ahora cambia
a
El resultado ahora es:
Así que ahora estamos descargando en serie, que es el problema originalmente resuelto.
adjuntar
De acuerdo con el principio de TIMTOWTDI, podemos encadenar a los editores
append
para serializarlos:El resultado es una editorial que serializa a las editoriales retrasadas en la colección original. Probémoslo suscribiéndolo:
Efectivamente, los enteros ahora llegan en orden (con intervalos aleatorios entre).
Podemos encapsular la creación de
pub
una colección de editores con una extensión en Colección, como lo sugiere Clay Ellis:fuente
Aquí hay una página de código de juegos que muestra un posible enfoque. La idea principal es transformar las llamadas API asíncronas en una cadena de
Future
editores, haciendo así una canalización en serie.Entrada: rango de int de 1 a 10 que asíncronamente en la cola de fondo convertida en cadenas
Demostración de llamada directa a API asíncrona:
Salida:
Demostración de la tubería combinada:
Salida:
Código:
fuente
Usar
flatMap(maxPublishers:transform:)
con.max(1)
, p. Ej.Dónde
y
Eso resultó en:
Pero debemos reconocer que recibes un gran éxito de rendimiento haciéndolos secuencialmente, así. Por ejemplo, si lo aumento hasta 6 a la vez, es más del doble de rápido:
Personalmente, recomendaría descargar solo secuencialmente si es absolutamente necesario (lo cual, al descargar una serie de imágenes / archivos, casi seguramente no es el caso). Sí, realizar solicitudes simultáneamente puede hacer que no terminen en un orden particular, pero solo usamos una estructura que es independiente del orden (por ejemplo, un diccionario en lugar de una matriz simple), pero las ganancias de rendimiento son tan significativas que generalmente vale la pena.
Pero, si desea que se descarguen secuencialmente, el
maxPublishers
parámetro puede lograrlo.fuente
maxPublishers
opción. Y no me habría molestado sobre "no hacer serial" si me hubiera dado cuenta de que eras tú (ya que sé que entiendes completamente los pros y los contras de serial versus concurrente). Literalmente solo vi "Quiero descargar un archivo a la vez", recientemente me topé con lamaxPublishers
opción de otra cosa que estaba haciendo (a saber, proporcionar una solución moderna a esta pregunta ), y pensé en compartir la solución Combinar. se le ocurrió. No quise ser tan derivado.