GCD do wykonania zadania w głównym wątku


254

Mam wywołanie zwrotne, które może pochodzić z dowolnego wątku. Kiedy otrzymuję to wywołanie zwrotne, chciałbym wykonać pewne zadanie w głównym wątku.

Czy muszę sprawdzać, czy jestem już w głównym wątku - czy istnieje kara za nieprzeprowadzenie tej kontroli przed wywołaniem poniższego kodu?

dispatch_async(dispatch_get_main_queue(), ^{
   // do work here
});

116
Pięć lat później nadal nie pamiętam składni bloków GCD i za każdym razem trafiam tutaj.
— SpaceTrucker

7
@SpaceTrucker - Z tego samego powodu jestem na tej stronie: D
— Matthew Cawley

4
9 lat później nadal przychodzę kopiować składnię z tej strony.
— Osa

Odpowiedzi:


152

Nie, nie musisz sprawdzać, czy jesteś już w głównym wątku. Wysyłając blok do kolejki głównej, planujesz tylko szeregowe wykonanie bloku w głównym wątku, co dzieje się, gdy uruchomiona zostanie odpowiednia pętla uruchamiania.

Jeśli jesteś już w głównym wątku, zachowanie jest takie samo: blok jest zaplanowany i wykonywany, gdy uruchomiona zostanie pętla uruchamiania głównego wątku.


3
Pytanie brzmiało, czy istnieje „kara za nieprzeprowadzenie tej kontroli” ... Wydaje mi się, że istnieje kara wydajności za użycie wysyłki asynchronicznej, gdy nie jest to konieczne, czy jest to trywialne?
— Dan Rosenstark,

1
@ Yar Nie sądzę, aby w większości przypadków zauważalny był wpływ na wydajność: GCD to lekka biblioteka. To powiedziawszy, zrozumiałem pytanie jako: „biorąc pod uwagę poniższy kod, czy muszę sprawdzić, czy jestem w głównym wątku?”

7
Musisz jednak sprawdzić, czy używasz dispatch_sync. W przeciwnym razie dostaniesz impas.
— Victor Engel

Jeśli jesteś w kolejce głównej i wysyłasz z asyncpowrotem do kolejki głównej, zostanie ona uruchomiona, ale może to zepsuć oczekiwany czas Twoich działań . Takie jak kod UI w viewDidLoad() nie działa, dopóki po raz pierwszy zostanie wyświetlony widok .
— pkamb

106

W przypadku asynchronicznego przypadku wysyłki opisanego powyżej nie trzeba sprawdzać, czy jesteś w głównym wątku. Jak wskazuje Bavarious, będzie to po prostu w kolejce do uruchomienia w głównym wątku.

Jeśli jednak spróbujesz wykonać powyższe czynności za pomocą a, dispatch_sync()a Twoje wywołanie zwrotne będzie w głównym wątku, aplikacja zablokuje się w tym momencie. Opisuję to w mojej odpowiedzi tutaj , ponieważ to zachowanie zaskoczyło mnie podczas przenoszenia kodu -performSelectorOnMainThread:. Jak wspomniałem, stworzyłem funkcję pomocnika:

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

który uruchomi blok synchronicznie w głównym wątku, jeśli aktualnie używana metoda nie znajduje się w głównym wątku, i po prostu wykona blok wstawiony, jeśli tak jest. Aby użyć tego, możesz użyć składni takiej jak poniżej:

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

1
Dlaczego nie nazwać tego „RunOnMainQueueSync”? Fakt, że może się zakleszczyć, a nie jest, jest czymś, czego nie chciałbym mieć w całym kodzie. Dzięki i +1 jak zawsze.
— Dan Rosenstark,

2
@Yar - właśnie nadałem mu tę nazwę, aby było dla mnie jasne, dlaczego nie korzystam z synchronicznego uruchamiania bloków w głównej kolejce zamiast korzystania z tej funkcji pomocnika. To było dla mnie bardziej przypomnienie.
— Brad Larson

3
Dzięki tej funkcji nadal możliwe jest zakleszczenie. Synchronizacja kolejki głównej> synchronizacja innej kolejki> kolejka główna zostanie zakleszczona.
— hfossli

2
@hfossli - Prawda, nie poradzi sobie z każdą sprawą. W moim użyciu zawsze albo dzwoniłem z asynchronicznej wysyłki w kolejce szeregowej w tle, albo wstawiałem kod w głównym wątku. Zastanawiam się, czy dispatch_set_specific()pomógłby w opisanym przypadku: stackoverflow.com/a/12806754/19679 .
— Brad Larson

1
[NSThread isMainThread] często zwraca TAK i nie jest uważane za bezpieczne do sprawdzania tego przypadku w programowaniu GCD. stackoverflow.com/questions/14716334/…
— Will Larche

57

Jak wspomniano w innych odpowiedziach, dispatch_async z głównego wątku jest w porządku.

Jednak w zależności od przypadku użycia istnieje efekt uboczny, który możesz uznać za wadę: ponieważ blok jest zaplanowany w kolejce, nie zostanie on wykonany, dopóki kontrola nie wróci do pętli uruchamiania, co spowoduje opóźnienie wykonanie twojego bloku.

Na przykład,

NSLog(@"before dispatch async");
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"inside dispatch async block main thread from main thread");
});
NSLog(@"after dispatch async");

Wydrukuje:

before dispatch async
after dispatch async
inside dispatch async block main thread from main thread

Z tego powodu, jeśli spodziewałeś się, że blok wykona się pomiędzy zewnętrznymi NSLogami, dispatch_async nie pomoże.


Trzeba powiedzieć, że jest to ważna rzecz do rozważenia
— Marc-Alexandre Bérubé

Ponieważ chcesz to sprawdzić, oznacza to, że kod może, ale nie musi, działać w głównym wątku. Już w głównym wątku będzie działał w tej kolejności, w przeciwnym razie nie można tego zagwarantować. Dlatego należy unikać projektowania kodu, który będzie działał w określonej kolejności, gdy będzie mowa o programowaniu wielowątkowym. Spróbuj użyć asynchronicznego sposobu wywołania zwrotnego.
— ooops

1

Nie, nie musisz sprawdzać, czy jesteś w głównym wątku. Oto, jak możesz to zrobić w Swift:

runThisInMainThread { () -> Void in
    runThisInMainThread { () -> Void in
        // No problem
    }
}

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

Jest zawarty jako standardowa funkcja w moim repozytorium, sprawdź to: https://github.com/goktugyil/EZSwiftExtensions

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.