iPhone - Discussione principale di Grand Central Dispatch


145

Ho usato con successo, invio centralizzato nelle mie app, ma mi chiedevo quale sia il vero vantaggio di usare qualcosa del genere:

dispatch_async(dispatch_get_main_queue(), ^{ ... do stuff

o anche

dispatch_sync(dispatch_get_main_queue(), ^{ ... do stuff

Voglio dire, in entrambi i casi stai lanciando un blocco da eseguire sul thread principale, esattamente dove viene eseguita l'app e questo non aiuterà a ridurre il carico. Nel primo caso non hai alcun controllo su quando verrà eseguito il blocco. Ho visto casi di blocchi eseguiti mezzo secondo dopo che li hai sparati. Il secondo caso, è simile a

[self doStuff];

giusto?

Mi chiedo cosa ne pensate voi ragazzi.


9
A proposito, lanciare una coda principale in dispatch_sync comporterà un deadlock.
Brooks Hanes,

5
Basta leggerlo nei documenti: "A differenza di dispatch_async, [dispatch_sync] non ritorna fino al termine del blocco. Chiamare questa funzione e indirizzare la coda corrente si traduce in deadlock." ... Ma forse sto leggendo questo errore ... ( la coda corrente non significa thread principale). Per favore, correggi se sbaglio.
Brooks Hanes,

4
@BrooksHanes non è sempre vero. Ne risulterà un deadlock se sei già sul thread principale. In caso contrario, non ci sarebbe un punto morto. Vedi qui
Honey,

Risposte:


296

L'invio di un blocco alla coda principale viene in genere eseguito da una coda in background per segnalare che l'elaborazione in background è terminata, ad es

- (void)doCalculation
{
    //you can use any string instead "com.mycompany.myqueue"
    dispatch_queue_t backgroundQueue = dispatch_queue_create("com.mycompany.myqueue", 0);

    dispatch_async(backgroundQueue, ^{
        int result = <some really long calculation that takes seconds to complete>;

        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateMyUIWithResult:result];
        });    
    });
}

In questo caso, stiamo eseguendo un lungo calcolo su una coda in background e dobbiamo aggiornare la nostra UI al termine del calcolo. L'aggiornamento dell'interfaccia utente normalmente deve essere eseguito dalla coda principale, quindi "segnaliamo" di nuovo alla coda principale utilizzando un secondo dispatch_async nidificato.

Probabilmente ci sono altri esempi in cui potresti voler rispedire alla coda principale, ma generalmente viene fatto in questo modo, cioè nidificato da un blocco inviato a una coda in background.

  • elaborazione in background terminata -> aggiorna UI
  • blocco di dati elaborati sulla coda in background -> segnala la coda principale per avviare il blocco successivo
  • dati di rete in entrata sulla coda in background -> segnala la coda principale che il messaggio è arrivato
  • ecc ecc

Per quanto riguarda il motivo per cui potresti voler inviare alla coda principale dalla coda principale ... Beh, in genere non lo faresti, anche se immaginabilmente potresti farlo per pianificare un lavoro da eseguire la prossima volta intorno al ciclo di esecuzione.


Ah, capisco. Quindi ho ragione. Non vi è alcun vantaggio nel farlo se si è già nella coda principale, solo se ci si trova in un'altra coda e si desidera aggiornare l'interfaccia utente. Grazie.
Duck,

Ho appena modificato la mia risposta per parlare del perché non è molto utile farlo dalla coda principale.
Robin Summerhill,

Inoltre, penso che ci sia un bug in iOS 4 (potrebbe essere andato in iOS 5), in cui dispatch_sync alla coda principale dal thread principale provoca solo un blocco, quindi eviterei di farlo completamente.
Joerick,

10
Non è un bug, questo è il comportamento previsto. Certo, comportamento non molto utile, ma è sempre necessario essere consapevoli dei deadlock quando si utilizza dispatch_sync. Non puoi aspettarti che il sistema ti protegga continuamente dall'errore del programmatore.
Robin Summerhill,

2
Che cos'è backgroundQueue qui? Come faccio a creare l'oggetto backgroundQueue
Nilesh Tupe l'

16

L'invio di blocchi alla coda principale dal thread principale può essere utile. Dà alla coda principale la possibilità di gestire altri blocchi che sono stati messi in coda in modo da non bloccare semplicemente tutto il resto dell'esecuzione.

Ad esempio, è possibile scrivere un server con thread essenzialmente singolo che gestisce comunque molte connessioni simultanee. Finché nessun singolo blocco nella coda impiega troppo tempo, il server rimane rispondente alle nuove richieste.

Se il tuo programma non fa altro che passare la sua intera vita a rispondere agli eventi, questo può essere del tutto naturale. Devi solo impostare i gestori di eventi in modo che vengano eseguiti sulla coda principale e quindi chiamare dispatch_main () e potresti non doverti preoccupare della sicurezza dei thread.


11

Spero di capire correttamente la tua domanda in quanto ti stai chiedendo delle differenze tra dispatch_async e dispatch_sync?

dispatch_async

invierà il blocco in una coda in modo asincrono. Ciò significa che invierà il blocco alla coda e non aspetterà che ritorni prima di continuare l'esecuzione del codice rimanente nel metodo.

dispatch_sync

invierà il blocco in una coda in modo sincrono. Ciò impedirà qualsiasi ulteriore esecuzione del codice rimanente nel metodo fino al completamento dell'esecuzione del blocco.

Ho usato principalmente una dispatch_asynccoda in background per ottenere lavoro dalla coda principale e sfruttare tutti i core extra che il dispositivo potrebbe avere. Quindi dispatch_asyncal thread principale se devo aggiornare l'interfaccia utente.

In bocca al lupo


1
grazie, ma chiedo i vantaggi dell'invio di qualcosa alla coda principale, trovandosi nella coda principale.
Duck,

9

Un punto in cui è utile è per le attività dell'interfaccia utente, come impostare uno spinner prima di un'operazione prolungata:

- (void) handleDoSomethingButton{

    [mySpinner startAnimating];

    (do something lengthy)
    [mySpinner stopAnimating];
}

non funzionerà, perché stai bloccando il thread principale durante la tua lunga operazione e non stai permettendo a UIKit di avviare effettivamente lo spinner.

- (void) handleDoSomethingButton{
     [mySpinner startAnimating];

     dispatch_async (dispatch_get_main_queue(), ^{
          (do something lengthy)
          [mySpinner stopAnimating];
    });
}

restituirà il controllo al ciclo di esecuzione, che pianificherà l'aggiornamento dell'interfaccia utente, avviando lo spinner, quindi toglierà la cosa successiva dalla coda di invio, che è l'elaborazione effettiva. Al termine dell'elaborazione, viene chiamato l'arresto dell'animazione e si ritorna al ciclo di esecuzione, in cui l'interfaccia utente viene quindi aggiornata con l'arresto.


@Jerceratops sì, ma consente il completamento del runloop corrente.
Dan Rosenstark,

3
Sì, ma è ancora terribile. Blocca ancora l'interfaccia utente. Potrei premere un altro pulsante subito dopo questo. Oppure prova e scorri. "(fai qualcosa di molto lungo)" non dovrebbe accadere sul thread principale e dispatch_async per consentire al pulsante di fare clic su "fine" non è una soluzione accettabile.
Jerceratops,

8

Swift 3, 4 e 5

Codice in esecuzione sul thread principale

DispatchQueue.main.async {
    // Your code here
}

1

Asincrono significa asincrono e dovresti usarlo la maggior parte delle volte. Non dovresti mai chiamare la sincronizzazione sul thread principale perché bloccherà l'interfaccia utente fino al completamento dell'attività. Ecco un modo migliore per farlo in Swift:

runThisInMainThread { () -> Void in
    // Run your code like this:
    self.doStuff()
}

func runThisInMainThread(block: dispatch_block_t) {
    dispatch_async(dispatch_get_main_queue(), block)
}

È incluso come funzione standard nel mio repository, provalo: https://github.com/goktugyil/EZSwiftExtensions

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.