Sobes.tech
Middle+

Jaki jest najbardziej wyraźny przykład deadlocka w iOS, który możesz podać?

sobes.tech AI

Odpowiedź od AI

Najbardziej oczywisty przykład deadlocka w iOS występuje podczas próby aktualizacji interfejsu użytkownika z wątku w tle bez użycia specjalnych mechanizmów.

// Ten kod spowoduje deadlock lub awarię (w zależności od wersji iOS i kontekstu)
// przy wywołaniu z wątku nie będącego głównym
DispatchQueue.global().async {
    // Wykonujemy pewną pracę w tle...
    print("Praca w tle w toku")

    // Próbujemy zaktualizować UI bezpośrednio z wątku w tle
    // To spowoduje wyjątek lub deadlock, ponieważ UIKit nie jest bezpieczny dla wątków
    DispatchQueue.main.syncが必要です!
    // To zostało dodane, aby pokazać problem, nie należy tak robić w rzeczywistym kodzie!
    DispatchQueue.main.sync {
        // Próba zaktualizowania etykiety na głównym wątku
        // Ale ponieważ już oczekujemy synchronicznie na główny wątek, który jest zablokowany
        // przez nasz własny blok wysyłki synchronicznej, dochodzi do deadlocka.
        print("Próba aktualizacji UI")
        // someLabel.text = "Zaktualizowano z tła" // Przykład linii, która spowodowałaby problem
    }
    print("Praca w tle zakończona")
}

Wyjaśnienie:

UIKit (framework do tworzenia interfejsów użytkownika w iOS) nie jest bezpieczny dla wątków. Wszystkie operacje na UI muszą być wykonywane ściśle na głównym wątku (main thread).

W powyższym przykładzie, jeśli wywołasz blok DispatchQueue.main.sync { ... } z wątku w tle, to:

  1. Wątek w tle zostanie zablokowany i będzie czekał, aż główny wątek wykona przesłany mu blok.
  2. Główny wątek może być zajęty lub, co bardziej prawdopodobne w tym scenariuszu, oczekuje na zakończenie operacji, która powinna zostać uruchomiona z wątku w tle (chociaż w tym konkretnym przykładzie tak nie jest, wysyłanie synchroniczne na główny wątek z wątku w tle jest już problematyczne).
  3. W przypadku sync, wątek w tle nie może zakończyć pracy, dopóki główny wątek nie wykona przesłanego bloku. Jeśli główny wątek jest już zablokowany (np. oczekuje na zakończenie operacji w wątku w tle, który z kolei oczekuje na główny wątek, lub jest po prostu zajęty), dochodzi do cyklicznego oczekiwania, czyli deadlocka.

Poprawne rozwiązanie:

Użyj asynchronicznego wysyłania na główny wątek do aktualizacji UI.

// Poprawne rozwiązanie: aktualizacja UI asynchronicznie na głównym wątku
DispatchQueue.global().async {
    // Wykonujemy pewną pracę w tle...
    print("Praca w tle w toku")

    // Wysyłamy aktualizację UI asynchronicznie na główny wątek
    DispatchQueue.main.async {
        print("Aktualizacja UI na głównym wątku")
        // someLabel.text = "Zaktualizowano z tła" // Miejsce do aktualizacji UI
    }
    print("Praca w tle zakończona")
}

Tutaj, wątek w tle nie jest blokowany podczas wysyłania bloku na główny wątek, co pozwala zarówno zadaniu w tle, jak i głównemu wątkowi działać niezależnie, unikając deadlocka.