Mam algorytm generowania poziomów, który jest ciężki obliczeniowo. Wywołanie go zawsze powoduje zawieszenie się ekranu gry. Jak mogę umieścić funkcję w drugim wątku, gdy gra nadal wyświetla ekran ładowania, aby wskazać, że gra się nie zawiesiła?
unity
multithreading
DarkDestry
źródło
źródło
List
Akcji do przechowywania funkcji, które chcesz wywołać w głównym wątku. zablokuj i skopiujAction
listę wUpdate
funkcji do listy tymczasowej, wyczyść oryginalną listę, a następnie wykonajAction
kod w tymList
w głównym wątku. Zobacz UnityThread z mojego innego postu, jak to zrobić. Na przykład, aby wywołać funkcję w głównym wątku,UnityThread.executeInUpdate(() => { transform.Rotate(new Vector3(0f, 90f, 0f)); });
Odpowiedzi:
Aktualizacja: W 2018 roku Unity wprowadza system zadań C # jako sposób na odciążenie pracy i wykorzystanie wielu rdzeni procesora.
Poniższa odpowiedź poprzedza ten system. Nadal będzie działać, ale mogą być lepsze opcje dostępne w nowoczesnej Unity, w zależności od potrzeb. W szczególności wydaje się, że system zadań rozwiązuje niektóre z ograniczeń, do których ręcznie tworzone wątki mogą bezpiecznie uzyskać dostęp, opisanych poniżej. Na przykład programiści eksperymentujący z raportem podglądu wykonujący raycast i tworzący równolegle siatki .
Zapraszam użytkowników z doświadczeniem w korzystaniu z tego systemu zadań do dodawania własnych odpowiedzi odzwierciedlających bieżący stan silnika.
W przeszłości korzystałem z wątków do zadań ciężkich w Unity (zwykle przetwarzanie obrazów i geometrii) i nie różni się drastycznie od używania wątków w innych aplikacjach C #, z dwoma zastrzeżeniami:
Ponieważ Unity używa nieco starszego podzbioru .NET, istnieje kilka nowszych funkcji wątków i bibliotek, których nie możemy używać od razu po wyjęciu z pudełka, ale podstawy są już dostępne.
Jak zauważa Almo w powyższym komentarzu, wiele typów Jedności nie jest bezpiecznych dla wątków i spowoduje wyjątki, jeśli spróbujesz je skonstruować, użyć, a nawet porównać z głównym wątkiem. O czym należy pamiętać:
Jednym z typowych przypadków jest sprawdzenie, czy referencja GameObject lub Monobehaviour jest zerowa przed próbą uzyskania dostępu do jej członków.
myUnityObject == null
wywołuje przeciążonego operatora dla wszystkiego, co wywodzi się z UnityEngine.Object, aleSystem.Object.ReferenceEquals()
działa w tym zakresie do pewnego stopnia - pamiętaj tylko, że GameObject z Destroy () porównuje wartość równą zeru przy użyciu przeciążenia, ale nie jest jeszcze ReferenceEqual do null.Odczytywanie parametrów z typów Unity jest zwykle bezpieczne w innym wątku (w tym sensie, że nie od razu wyrzuci wyjątek, o ile będziesz uważnie sprawdzać wartości zerowe jak wyżej), ale zwróć uwagę na ostrzeżenie Filipa, że główny wątek może modyfikować stan podczas gdy to czytasz. Musisz być zdyscyplinowany, kto może modyfikować, co i kiedy, aby uniknąć odczytania niespójnego stanu, co może prowadzić do błędów, które mogą być diabelnie trudne do wyśledzenia, ponieważ zależą one od mniej niż milisekundowych czasów między wątkami, które możemy rozmnażają się do woli.
Statyczne elementy losowe i czasowe nie są dostępne. Utwórz instancję System.Random dla wątku, jeśli potrzebujesz losowości, i System.Diagnostics.Stopwatch, jeśli potrzebujesz informacji o taktowaniu.
Funkcje Mathf, struktury wektorowe, macierzowe, czwartorzędowe i kolorowe działają dobrze w wątkach, dzięki czemu większość obliczeń można wykonać osobno
Tworzenie GameObjects, dołączanie Monobehaviours lub tworzenie / aktualizowanie tekstur, siatek, materiałów itp. - wszystko to musi się zdarzyć w głównym wątku. W przeszłości, kiedy musiałem z nimi pracować, tworzyłem kolejkę producent-konsument, w której mój wątek roboczy przygotowuje surowe dane (takie jak duży wektory / kolory do zastosowania na siatce lub teksturze), a Aktualizacja lub Coroutine w głównym wątku sonduje dane i stosuje je.
Z tymi notatkami na bok, oto wzór, którego często używam do pracy w wątkach. Nie gwarantuję, że jest to styl najlepszych praktyk, ale spełnia swoje zadanie. (Komentarze lub zmiany do ulepszenia są mile widziane - Wiem, że wątki to bardzo głęboki temat, którego znam tylko podstawy)
Jeśli nie potrzebujesz ściśle dzielić pracy na wątki, aby uzyskać szybkość, a szukasz sposobu, aby nie blokować, aby reszta gry wciąż działała, lekkim rozwiązaniem w Unity są Coroutines . Są to funkcje, które mogą wykonać trochę pracy, a następnie przywrócić kontrolę nad silnikiem, aby kontynuować to, co robi, i płynnie wznowić w późniejszym czasie.
Nie wymaga to żadnych specjalnych rozważań dotyczących czyszczenia, ponieważ silnik (o ile wiem) pozbywa się dla ciebie korupcji ze zniszczonych obiektów.
Cały lokalny stan metody jest zachowywany, gdy ustępuje i wznawia się, więc dla wielu celów działa tak, jakby działał nieprzerwanie w innym wątku (ale masz wszystkie wygody działania w głównym wątku). Musisz tylko upewnić się, że każda iteracja jest wystarczająco krótka, aby nie spowolnić twojego głównego wątku w nieuzasadniony sposób.
Zapewniając, że ważne operacje nie są oddzielone wydajnością, możesz uzyskać spójność zachowania jednowątkowego - wiedząc, że żaden inny skrypt lub system w głównym wątku nie może modyfikować danych, nad którymi pracujesz.
Linia zwrotu z zysku daje kilka opcji. Możesz...
yield return null
wznowić po aktualizacji następnej ramki ()yield return new WaitForFixedUpdate()
wznowić po następnej FixedUpdate ()yield return new WaitForSeconds(delay)
wznowić po upływie określonego czasu gryyield return new WaitForEndOfFrame()
wznowić po zakończeniu renderowania GUIyield return myRequest
gdziemyRequest
jest instancja WWW , która jest wznawiana po zakończeniu ładowania żądanych danych z sieci lub dysku.yield return otherCoroutine
gdzieotherCoroutine
jest instancja Coroutine , którą można wznowić pootherCoroutine
zakończeniu. Jest to często używane w formularzuyield return StartCoroutine(OtherCoroutineMethod())
do łączenia wykonania z nową korupcją, która sama może ustąpić, kiedy chce.Eksperymentalnie, pomijanie drugiego
StartCoroutine
i pisanie po prostuyield return OtherCoroutineMethod()
osiąga ten sam cel, jeśli chcesz wykonać łańcuch w tym samym kontekście.Zawijanie wewnątrz
StartCoroutine
może być nadal przydatne, jeśli chcesz uruchomić zagnieżdżoną coroutine w połączeniu z drugim obiektem, takim jakyield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())
... w zależności od tego, kiedy chcesz, aby coroutine wzięła kolejną turę.
Lub,
yield break;
aby zatrzymać coroutine, zanim dotrze do końca, sposób, w jaki możesz użyćreturn;
do wczesnego wyjścia z konwencjonalnej metody.źródło
Możesz umieścić swoje ciężkie obliczenia w innym wątku, ale interfejs API Unity nie jest bezpieczny dla wątku, musisz wykonać je w głównym wątku.
Możesz wypróbować ten pakiet w Asset Store, który ułatwi ci korzystanie z wątków. http://u3d.as/wQg Możesz po prostu użyć tylko jednego wiersza kodu, aby uruchomić wątek i bezpiecznie uruchomić interfejs Unity API.
źródło
Za pomocą Svelto.Tasks możesz dość łatwo zwrócić wynik wielowątkowej procedury do głównego wątku (a zatem i funkcji jedności):
http://www.sebaslab.com/svelto-taskrunner-run-serial-and-parallel-asynchronous-tasks-in-unity3d/
źródło
@DMGregory wyjaśnił to bardzo dobrze.
Można używać zarówno wątków, jak i coroutines. Poprzednio w celu odciążenia głównego wątku, a później w celu przywrócenia kontroli do głównego wątku. Pchanie ciężkiego podnoszenia do oddzielnego gwintu wydaje się bardziej rozsądne. Dlatego kolejki zadań mogą być tym, czego szukasz.
Na Unity Wiki jest naprawdę dobry przykładowy skrypt JobQueue .
źródło