Estou testando algum código que faz processamento assíncrono usando o Grand Central Dispatch. O código de teste fica assim:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
Os testes precisam aguardar a conclusão da operação. Minha solução atual é assim:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
O que parece um pouco bruto, você conhece uma maneira melhor? Eu poderia expor a fila e depois bloquear chamando dispatch_sync
:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
... mas isso talvez exponha muito o object
.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
comwhile (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; }
Além da técnica do semáforo abordada exaustivamente em outras respostas, agora podemos usar o XCTest no Xcode 6 para executar testes assíncronos via
XCTestExpectation
. Isso elimina a necessidade de semáforos ao testar código assíncrono. Por exemplo:Para o bem dos futuros leitores, embora a técnica de semáforo de expedição seja uma técnica maravilhosa quando absolutamente necessária, devo confessar que vejo muitos desenvolvedores novos, não familiarizados com bons padrões de programação assíncronos, gravitando muito rapidamente em semáforos como um mecanismo geral para tornar assíncrona rotinas se comportam de forma síncrona. Pior, já vi muitos deles usarem essa técnica de semáforo na fila principal (e nunca devemos bloquear a fila principal nos aplicativos de produção).
Sei que esse não é o caso aqui (quando esta pergunta foi publicada, não havia uma ferramenta interessante como
XCTestExpectation
; também, nesses conjuntos de testes, devemos garantir que o teste não termine até que a chamada assíncrona seja concluída). Essa é uma daquelas situações raras em que a técnica de semáforo para bloquear o encadeamento principal pode ser necessária.Portanto, com minhas desculpas ao autor desta pergunta original, para quem a técnica de semáforo é sólida, escrevo este aviso a todos os novos desenvolvedores que veem essa técnica de semáforo e considero aplicá-la em seu código como uma abordagem geral para lidar com assinaturas assíncronas. métodos: esteja avisado que nove em cada dez vezes, a técnica do semáforo não éa melhor abordagem ao contar operações assíncronas. Em vez disso, familiarize-se com os padrões de bloqueio / fechamento de conclusão, bem como com padrões e notificações de protocolo de delegação. Geralmente, essas são maneiras muito melhores de lidar com tarefas assíncronas, em vez de usar semáforos para fazê-las se comportar de maneira síncrona. Geralmente, existem boas razões para as tarefas assíncronas terem sido projetadas para se comportarem de maneira assíncrona, portanto, use o padrão assíncrono correto, em vez de tentar fazê-las se comportar de maneira síncrona.
fonte
NSOperationQueue
. A menos que eu use algo como um semáforo, todos os downloads de documentosNSOperation
aparecerão imediatamente completos e não haverá fila de downloads real - eles continuarão simultaneamente, o que eu não quero. Os semáforos são razoáveis aqui? Ou existe uma maneira melhor de fazer as NSOperations aguardarem o fim assíncrono de outras pessoas? Ou alguma outra coisa?AFHTTPRequestOperation
objetos, crie uma operação de conclusão (a qual dependerá das outras operações). Ou use grupos de expedição. BTW, você diz que não deseja que eles sejam executados simultaneamente, o que é bom se for o que você precisa, mas você paga uma séria penalidade de desempenho fazendo isso sequencialmente e não simultaneamente. Eu geralmente usomaxConcurrentOperationCount
4 ou 5.Recentemente, vim a esse problema novamente e escrevi a seguinte categoria em
NSObject
:Dessa forma, posso facilmente transformar chamadas assíncronas com retorno de chamada em síncronas nos testes:
fonte
Geralmente, não use nenhuma dessas respostas, elas geralmente não serão dimensionadas (há exceções aqui e ali, com certeza)
Essas abordagens são incompatíveis com a forma como o GCD se destina a funcionar e acabam causando conflitos e / ou morte da bateria por meio de pesquisas ininterruptas.
Em outras palavras, reorganize seu código para que não haja espera síncrona por um resultado, mas lide com um resultado sendo notificado de alteração de estado (por exemplo, retornos de chamada / protocolos de delegados, disponibilidade, partida, erros, etc.). (Eles podem ser refatorados em blocos se você não gostar do inferno de retorno de chamada.) Como é assim, expor o comportamento real ao restante do aplicativo e ocultá-lo atrás de uma fachada falsa.
Em vez disso, use o NSNotificationCenter , defina um protocolo de delegação personalizado com retornos de chamada para sua classe. E se você não gosta de mexer com os retornos de chamada delegados, envolva-os em uma classe proxy concreta que implemente o protocolo personalizado e salve os vários blocos nas propriedades. Provavelmente também fornece construtores de conveniência também.
O trabalho inicial é um pouco mais alto, mas reduzirá o número de péssimas condições de corrida e pesquisas sobre bateria a longo prazo.
(Não peça um exemplo, porque é trivial e também tivemos que investir tempo para aprender o básico do objetivo-c.)
fonte
Aqui está um truque bacana que não usa um semáforo:
O que você faz é esperar o uso
dispatch_sync
com um bloco vazio para aguardar de forma síncrona em uma fila de expedição serial até que o bloco A-Synchronous seja concluído.fonte
Exemplo de uso:
fonte
Há também o SenTestingKitAsync que permite escrever código como este:
(Veja o artigo objc.io para obter detalhes.) E como o Xcode 6 existe uma
AsynchronousTesting
categoriaXCTest
que permite escrever códigos como este:fonte
Aqui está uma alternativa de um dos meus testes:
fonte
NSCondition
documentação de-waitUntilDate:
"Você deve bloquear o receptor antes de chamar este método". Então o-unlock
deve ser depois-waitUntilDate:
.Isso fez por mim.
fonte
Às vezes, os loops de tempo limite também são úteis. Você pode esperar até receber algum sinal (pode ser BOOL) do método de retorno de chamada assíncrono, mas e se não houver resposta alguma e quiser sair desse loop? Aqui abaixo está a solução, mais respondida acima, mas com uma adição de Timeout.
fonte
Solução muito primitiva para o problema:
fonte
Swift 4:
Use em
synchronousRemoteObjectProxyWithErrorHandler
vez deremoteObjectProxy
ao criar o objeto remoto. Não há mais necessidade de um semáforo.O exemplo abaixo retornará a versão recebida do proxy. Sem o
synchronousRemoteObjectProxyWithErrorHandler
travamento (tentando acessar a memória não acessível):fonte
Eu tenho que esperar até que um UIWebView seja carregado antes de executar meu método. Consegui fazê-lo executando verificações prontas do UIWebView no thread principal usando o GCD em combinação com os métodos de semáforo mencionados neste thread. O código final fica assim:
fonte