Uwielbiam ankiety! Czy ja Tak! Czy ja Tak! Czy ja Tak! Czy nadal? Tak! Co teraz? Tak!
Jak wspomnieli inni, może być niewiarygodnie nieefektywny, jeśli będziesz sondować tylko po to, aby odzyskać ten sam niezmieniony stan w kółko. Taki jest przepis na spalanie cykli procesora i znaczne skrócenie żywotności baterii na urządzeniach mobilnych. Oczywiście nie jest to marnotrawstwo, jeśli odzyskujesz nowy i znaczący stan za każdym razem w tempie nie szybszym niż to pożądane.
Ale głównym powodem, dla którego uwielbiam ankiety, jest jej prostota i przewidywalność. Możesz prześledzić kod i łatwo zobaczyć, kiedy i gdzie coś się wydarzy oraz w jakim wątku. Jeśli teoretycznie żylibyśmy w świecie, w którym odpytywanie było znikomym marnotrawstwem (chociaż rzeczywistość jest daleka od tego), to uważam, że uprościłoby to utrzymywanie kodu jako ogromnej umowy. I to jest korzyść z odpytywania i ciągnięcia, ponieważ widzę, że moglibyśmy zignorować wydajność, chociaż nie powinniśmy w tym przypadku.
Kiedy zacząłem programować w erze DOS, moje małe gry obracały się wokół odpytywania. Skopiowałem część kodu asemblera z książki, którą ledwo rozumiałem, dotyczącą przerwania klawiatury i zapisałem bufor stanów klawiatury, w którym to momencie moja główna pętla zawsze odpytywała. Czy klawisz strzałki w górę jest wciśnięty? Nie. Czy klawisz strzałki w górę jest wciśnięty? Nie. A teraz? Nie. Teraz? Tak. Ok, rusz gracza.
I chociaż niezwykle marnotrawstwo, stwierdziłem, że o wiele łatwiej jest to uzasadnić w porównaniu do tych dni programowania wielozadaniowego i zdarzeń. Wiedziałem dokładnie, kiedy i gdzie coś się dzieje przez cały czas, i łatwiej było utrzymać stabilną i przewidywalną liczbę klatek bez czkawki.
Od tego czasu zawsze starałem się znaleźć sposób na uzyskanie niektórych korzyści i przewidywalności tego bez faktycznego spalania cykli procesora, takich jak używanie zmiennych warunkowych do powiadamiania wątków o przebudzeniu, w którym momencie mogą pobrać nowy stan, robić swoje i wracać do łóżka, czekając na powiadomienie.
I w jakiś sposób uważam, że kolejki zdarzeń są łatwiejsze do pracy z wzorcami niż obserwatorami, mimo że nadal nie są tak łatwe do przewidzenia, dokąd się wybierzesz i co się stanie. Przynajmniej scentralizują przepływ sterowania obsługą zdarzeń do kilku kluczowych obszarów w systemie i zawsze obsługują te zdarzenia w tym samym wątku zamiast podskakiwać z jednej funkcji do miejsca całkowicie odległego i niespodziewanego poza centralnym wątkiem obsługi zdarzeń. Tak więc dychotomia nie zawsze musi odbywać się między obserwatorami a ankietami. Kolejki zdarzeń są jakby pośrednikiem.
Ale tak, w jakiś sposób uważam, że o wiele łatwiej jest rozumować systemy, które wykonują rzeczy, które są analogicznie bliższe do rodzaju przewidywalnych przepływów kontrolnych, które miałem, kiedy sondowałem wieki temu, jednocześnie przeciwdziałając tendencji do pracy w czasy, kiedy nie nastąpiły żadne zmiany stanu. Jest więc taka korzyść, jeśli możesz to zrobić w sposób, który nie powoduje niepotrzebnego spalania cykli procesora, jak w przypadku zmiennych warunkowych.
Pętle homogeniczne
W porządku, otrzymałem świetny komentarz, Josh Caswell
który wskazał na pewną głupotę w mojej odpowiedzi:
„jak używanie zmiennych warunkowych do powiadamiania wątków o przebudzeniu” Brzmi jak układ oparty na zdarzeniu / obserwatorze, a nie odpytywanie
Technicznie sama zmienna warunku stosuje wzorzec obserwatora do budzenia / powiadamiania wątków, więc nazwanie tego „odpytywaniem” byłoby prawdopodobnie niezwykle mylące. Ale uważam, że zapewnia podobną korzyść, jaką znalazłem jako odpytywanie z dni DOS (tylko pod względem przepływu kontroli i przewidywalności). Spróbuję to wyjaśnić lepiej.
W tamtych czasach podobało mi się to, że możesz spojrzeć na sekcję kodu lub prześledzić ją i powiedzieć: „Okej, cała ta sekcja jest poświęcona obsłudze zdarzeń na klawiaturze. W tej sekcji kodu nic więcej się nie wydarzy I wiem dokładnie, co się wydarzy wcześniej i wiem dokładnie, co się wydarzy później (np. Fizyka i rendering). ” Sondowanie stanów klawiatury zapewniło tego rodzaju centralizację przepływu kontroli w zakresie obsługi tego, co powinno nastąpić w odpowiedzi na to zdarzenie zewnętrzne. Nie zareagowaliśmy natychmiast na to zdarzenie zewnętrzne. Odpowiedzieliśmy na to w dogodnym dla nas czasie.
Kiedy używamy systemu opartego na wypychaniu opartego na wzorcu obserwatora, często tracimy te korzyści. Formant może zostać przeskalowany, co wyzwala zdarzenie zmiany rozmiaru. Kiedy go prześledzimy, stwierdzimy, że znajdujemy się w egzotycznej kontroli, która zmienia wiele niestandardowych rzeczy, co powoduje więcej zdarzeń. W efekcie jesteśmy całkowicie zaskoczeni śledzeniem wszystkich tych kaskadowych zdarzeń, co do tego, gdzie skończymy w systemie. Co więcej, możemy odkryć, że wszystko to nawet nie występuje konsekwentnie w danym wątku, ponieważ wątek A może tutaj zmienić rozmiar kontrolki, podczas gdy wątek B również zmienia rozmiar kontrolki później. Dlatego zawsze uważałem to za bardzo trudne do przewidzenia, biorąc pod uwagę, jak trudno jest przewidzieć, gdzie wszystko się wydarzy, a także co się stanie.
Kolejka zdarzeń jest dla mnie trochę łatwiejsza do uzasadnienia, ponieważ upraszcza to, gdzie wszystkie te rzeczy dzieją się przynajmniej na poziomie wątku. Jednak może się zdarzyć wiele różnych rzeczy. Kolejka zdarzeń może zawierać eklektyczną mieszankę zdarzeń do przetworzenia, a każda z nich może nas jeszcze zaskoczyć, jaka kaskada zdarzeń się wydarzyła, kolejność ich przetwarzania i jak odbijamy się po całym miejscu w bazie kodu .
To, co uważam za „najbliższe” odpytywaniu, nie użyłoby kolejki zdarzeń, ale odroczyłoby bardzo jednorodny rodzaj przetwarzania. A PaintSystem
może zostać zaalarmowany przez zmienną warunkową, że do odmalowania niektórych komórek siatki okna należy wykonać malowanie, w którym to momencie wykonuje prostą sekwencyjną pętlę przez komórki i odświeża wszystko w środku w odpowiedniej kolejności Z. Może istnieć jeden poziom wywołania pośredniej / dynamicznej wysyłki, aby wywołać zdarzenia malowania w każdym widgecie znajdującym się w komórce, którą należy odmalować, ale to wszystko - tylko jedna warstwa wywołań pośrednich. Zmienna warunkowa używa wzorca obserwatora, aby ostrzec PaintSystem
, że ma pracę do wykonania, ale nie określa nic więcej, aPaintSystem
jest w tym momencie poświęcony jednemu jednorodnemu, bardzo jednorodnemu zadaniu. Kiedy debugujemy i PaintSystem's
śledzimy kod, wiemy, że nic innego się nie wydarzy oprócz malowania.
Chodzi więc głównie o sprowadzenie systemu do miejsca, w którym te rzeczy wykonują jednorodne pętle w stosunku do danych, nakładając na to bardzo szczególną odpowiedzialność zamiast niejednorodnych pętli w stosunku do różnych typów danych, wykonując wiele obowiązków, jakie możemy uzyskać przy przetwarzaniu kolejki zdarzeń.
Dążymy do tego typu rzeczy:
when there's work to do:
for each thing:
apply a very specific and uniform operation to the thing
W przeciwieństwie do:
when one specific event happens:
do something with relevant thing
in relevant thing's event:
do some more things
in thing1's triggered by thing's event:
do some more things
in thing2's event triggerd by thing's event:
do some more things:
in thing3's event triggered by thing2's event:
do some more things
in thing4's event triggered by thing1's event:
cause a side effect which shouldn't be happening
in this order or from this thread.
I tak dalej. I nie musi to być jeden wątek na zadanie. Jeden wątek może zastosować logikę układów (zmiana rozmiaru / repozycjonowanie) do kontrolek GUI i odmalować je, ale może nie obsługiwać kliknięć klawiatury lub myszy. Możesz więc spojrzeć na to jako na poprawę jednorodności kolejki zdarzeń. Ale nie musimy też używać kolejki zdarzeń i przeplatać funkcji zmiany rozmiaru i malowania. Możemy zrobić jak:
in thread dedicated to layout and painting:
when there's work to do:
for each widget that needs resizing/reposition:
resize/reposition thing to target size/position
mark appropriate grid cells as needing repainting
for each grid cell that needs repainting:
repaint cell
go back to sleep
Tak więc powyższe podejście wykorzystuje tylko zmienną warunkową, aby powiadomić wątek, gdy jest praca do wykonania, ale nie przeplata różnych rodzajów zdarzeń (zmiana rozmiaru w jednej pętli, malowanie w innej pętli, a nie mieszanka obu) i nie „ t zawracał sobie głowę komunikowaniem, co dokładnie należy wykonać (wątek „odkrywa” to po przebudzeniu, patrząc na ogólnosystemowe stany ECS). Każda wykonywana przez niego pętla ma wtedy bardzo jednorodny charakter, co ułatwia ustalenie kolejności, w której wszystko się dzieje.
Nie jestem pewien, jak nazwać tego rodzaju podejście. Nie widziałem, żeby robiły to inne silniki GUI i jest to moje własne egzotyczne podejście do mojego. Ale zanim spróbowałem zaimplementować wielowątkowe frameworki GUI przy użyciu obserwatorów lub kolejek zdarzeń, miałem ogromne trudności z debugowaniem go, a także natknąłem się na pewne niejasne warunki wyścigowe i impasy, których nie byłem wystarczająco inteligentny, aby naprawić w sposób, który dał mi pewność siebie na temat rozwiązania (niektóre osoby mogą to zrobić, ale nie jestem wystarczająco inteligentny). Mój pierwszy projekt iteracji po prostu nazwał miejsce bezpośrednio przez sygnał, a niektóre miejsca następnie odradzały inne wątki, aby wykonać pracę asynchroniczną, i to było najtrudniejsze do uzasadnienia, a ja potykałem się o warunki wyścigu i impasy. Druga iteracja wykorzystywała kolejkę zdarzeń, co było nieco łatwiejsze do uzasadnienia, ale nie jest to wystarczająco łatwe, aby mój mózg mógł to zrobić, nie wpadając w niejasny impas i warunki wyścigu. Trzecia i ostatnia iteracja wykorzystała podejście opisane powyżej, a na koniec pozwoliło mi to na stworzenie wielowątkowego frameworku GUI, który nawet głupi prostak taki jak ja mógłby poprawnie zaimplementować.
Wtedy ten rodzaj ostatecznego wielowątkowego interfejsu GUI pozwolił mi wymyślić coś innego, co było o wiele łatwiejsze do uzasadnienia i uniknięcie tego rodzaju błędów, które zwykle popełniłem, i jeden z powodów, dla których tak łatwo było mi to uzasadnić w najmniej z powodu tych jednorodnych pętli i tego, jak trochę przypominały one sterowanie, podobnie jak podczas odpytywania w dniach DOS (nawet jeśli tak naprawdę nie odpytuje i wykonuje pracę tylko wtedy, gdy jest do zrobienia). Chodziło o to, aby odejść jak najdalej od modelu obsługi zdarzeń, co pociąga za sobą niejednorodne pętle, niejednorodne skutki uboczne, niejednorodne przepływy kontrolne, i coraz bardziej pracować w kierunku jednorodnych pętli działających jednorodnie na jednorodnych danych i izolowaniu i ujednolicające skutki uboczne w taki sposób, że łatwiej było skoncentrować się na „czym”