Como faço para que os pipelines assíncronos que constituem a estrutura Combine sejam alinhados de forma síncrona (serial)?
Suponha que eu tenha 50 URLs dos quais desejo fazer o download dos recursos correspondentes e digamos que eu queira fazer um de cada vez. Eu sei como fazer isso com Operation / OperationQueue, por exemplo, usando uma subclasse Operation que não se declara concluída até que o download seja concluído. Como eu faria a mesma coisa usando o Combine?
No momento, tudo o que me ocorre é manter uma lista global dos URLs restantes e abrir uma, configurar esse pipeline para um download, fazer o download e, no sink
pipeline, repetir. Isso não parece muito parecido com um Combine.
Tentei criar uma matriz de URLs e mapeá-la para uma variedade de editores. Eu sei que posso "produzir" um editor e fazê-lo publicar no pipeline usando flatMap
. Mas ainda estou fazendo o download simultaneamente. Não existe uma maneira combinada de percorrer a matriz de maneira controlada - ou existe?
(Também imaginei fazer algo com o Future, mas fiquei irremediavelmente confuso. Não estou acostumado a esse modo de pensar.)
append
é exatamente o que eu estava procurando. - Seu código pode ser consideravelmente mais rígido; em particular, não há necessidade de retornar prematuramente no caso em quecount == 1
, porque nesse casodropFirst
estará vazio e simplesmente não executaremos um loop. E não há necessidade de manter aoutput
variável, porque podemos usar emreduce
vez defor...in
. Veja minha resposta para obter uma renderização mais precisa.Você pode criar um assinante personalizado onde recebe Subscribers.Demand.max (1) retornado. Nesse caso, o assinante solicitará o próximo valor somente quando receber um. O exemplo é para Int.publisher, mas algum atraso aleatório no mapa imita o tráfego de rede :-)
Impressão para parque infantil ...
ATUALIZAÇÃO finalmente encontrei
.flatMap(maxPublishers: )
, o que me força a atualizar esse tópico interessante com uma abordagem um pouco diferente. Por favor, veja que estou usando a fila global para agendamento, não apenas algum atraso aleatório, apenas para garantir que o recebimento do fluxo serializado não seja um comportamento "aleatório" ou "sortudo" :-)impressões
Baseado aqui escrito
.serialize ()?
definido por Clay Ellis, a resposta aceita pode ser substituída por
.publisher.flatMap (maxPublishers: .max (1)) {$ 0}
enquanto a versão "não serializada" deve usar
.publisher.flatMap {$ 0}
"exemplo do mundo real"
impressões
Parece-me muito útil em outros cenários também. Tente usar o valor padrão de maxPublishers no próximo snippet e compare os resultados :-)
fonte
maxPublishers
parâmetro, adicionamos contrapressão. Isso acontece com o que eu disse na minha pergunta: "Eu sei que posso" produzir "um editor e fazê-lo publicar no pipeline usando o flatMap. Mas, ainda assim, continuo fazendo o download simultaneamente". Bem, com omaxPublishers
parâmetro, eles não são simultâneos.Em todas as outras estruturas reativas, isso é realmente fácil; você apenas usa
concat
para concatenar e nivelar os resultados em uma etapa e, em seguida, você podereduce
os resultados em uma matriz final. A Apple dificulta isso porquePublisher.Concatenate
não possui sobrecarga que aceite uma variedade de editores. Há estranheza semelhante comPublisher.Merge
. Sinto que isso tem a ver com o fato de que eles retornam editores genéricos aninhados em vez de apenas retornar um único tipo genérico como rx Observable. Eu acho que você pode simplesmente chamar Concatenarem um loop e, em seguida, reduza os resultados concatenados em uma única matriz, mas eu realmente espero que eles resolvam esse problema no próximo lançamento. Certamente, há a necessidade de concaturar mais de 2 publicadores e mesclar mais de 4 publicadores (e as sobrecargas para esses dois operadores nem são consistentes, o que é estranho).EDITAR:
Voltei a isso e descobri que você pode concatrar uma variedade arbitrária de editores e eles serão emitidos em sequência. Não tenho ideia de por que não existe uma função
ConcatenateMany
para fazer isso por você, mas parece que, desde que você esteja disposto a usar um editor de tipo apagado, não é tão difícil escrever você mesmo. Este exemplo mostra que a mesclagem emite na ordem temporal, enquanto concat emite na ordem da combinação:fonte
concat
serializado (nas outras estruturas reativas)?.append
é um operador que cria umPublisher.Concatenate
.Da pergunta original:
Aqui está um exemplo de brinquedo para substituir o verdadeiro problema:
Isso emite os números inteiros de 1 a 10 em ordem aleatória, chegando em momentos aleatórios. O objetivo é fazer algo com
collection
isso fará com que ele emita os números inteiros de 1 a 10 em ordem.Agora vamos mudar apenas uma coisa: na linha
nós adicionamos o
maxPublishers
parâmetro:Presto, nós agora fazer emitir os inteiros de 1 a 10, em ordem, com intervalos aleatórios entre eles.
Vamos aplicar isso ao problema original. Para demonstrar, preciso de uma conexão à Internet bastante lenta e de um recurso bastante grande para baixar. Primeiro, farei isso com o comum
.flatMap
:O resultado é
o que mostra que estamos fazendo os três downloads simultaneamente. Ok, agora mude
para
O resultado agora é:
Agora, estamos fazendo o download em série, que é o problema originalmente a ser resolvido.
acrescentar
De acordo com o princípio do TIMTOWTDI, podemos encadear os editores
append
para serializá-los:O resultado é um editor que serializa os editores atrasados na coleção original. Vamos provar isso assinando:
Com certeza, os números inteiros agora chegam em ordem (com intervalos aleatórios entre).
Podemos encapsular a criação de
pub
uma coleção de editores com uma extensão na coleção, conforme sugerido por Clay Ellis:fonte
Aqui está um código de playground de uma página que descreve uma possível abordagem. A idéia principal é transformar chamadas de API assíncronas em cadeia de
Future
editores, criando assim um pipeline serial.Entrada: intervalo de int de 1 a 10 que assina de forma assíncrona na fila de segundo plano convertida em cadeias
Demonstração de chamada direta para API assíncrona:
Resultado:
Demonstração do pipeline combinado:
Resultado:
Código:
fonte
Use
flatMap(maxPublishers:transform:)
com.max(1)
, por exemploOnde
e
Isso resultou em:
Mas devemos reconhecer que você recebe um grande sucesso de desempenho fazendo-os sequencialmente, assim. Por exemplo, se eu aumentar para 6 por vez, é mais do que o dobro da velocidade:
Pessoalmente, eu recomendaria o download sequencial apenas se for absolutamente necessário (o que, ao baixar uma série de imagens / arquivos, quase certamente não é o caso). Sim, a execução simultânea de solicitações pode fazer com que elas não terminem em uma ordem específica, mas apenas usamos uma estrutura que é independente da ordem (por exemplo, um dicionário em vez de uma matriz simples), mas os ganhos de desempenho são tão significativos que geralmente valem a pena.
Mas, se você deseja que eles sejam baixados sequencialmente, o
maxPublishers
parâmetro pode conseguir isso.fonte
maxPublishers
opção. E eu não teria falado sobre “não faça serial” se tivesse notado que era você (como eu sei que você entende completamente os prós e contras de serial versus simultâneo). Eu literalmente vi apenas "Quero baixar um arquivo de cada vez", encontrei recentemente amaxPublishers
opção de outra coisa que estava fazendo (a saber, fornecer uma solução moderna para essa pergunta ) e pensei em compartilhar a solução Combine I tinha inventado. Eu não quis ser tão derivado.