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.