2009-03-18 18 views
48

Jeśli pracowałeś z zestawami narzędzi GUI, wiesz, że istnieje pętla zdarzeń/pętla główna, która powinna zostać wykonana po wykonaniu wszystkich czynności, dzięki czemu aplikacja będzie nadal działać i reagować na różne zdarzenia. Na przykład, dla Qt, byś to zrobić w main():W jaki sposób zaimplementujesz podstawową pętlę zdarzeń?

int main() { 
    QApplication app(argc, argv); 
    // init code 
    return app.exec(); 
} 

który w tym przypadku, app.exec() jest główną pętlę aplikacji.

Oczywistym sposobem wdrożenia tego rodzaju pętli byłoby:

void exec() { 
    while (1) { 
     process_events(); // create a thread for each new event (possibly?) 
    } 
} 

Ale to czapki procesora do 100% i jest practicaly bezużyteczne. Jak mogę zaimplementować taką pętlę zdarzeń, która jest responsywna bez całkowitego zjedzenia procesora?

Odpowiedzi są doceniane w języku Python i/lub C++. Dzięki.

Przypis: W celu zdobycia wiedzy, zaimplementuję własne sygnały/gniazda, a ja użyłbym ich do wygenerowania zdarzeń niestandardowych (np. go_forward_event(steps)). Ale jeśli wiesz, jak ręcznie korzystać z zdarzeń systemowych, chciałbym o tym wiedzieć.

+3

wierzę, można zagłębić się w kodzie źródłowym Qt i zobaczyć dokładnie to, co robią w exec(). To prawdopodobnie dałoby ci kilka dobrych wskazówek. – JimDaniel

Odpowiedz

62

Często się zastanawiałem nad tym samym!

GUI główna pętla wygląda to w pseudo-kodzie:

void App::exec() { 
    for(;;) { 
     vector<Waitable> waitables; 
     waitables.push_back(m_networkSocket); 
     waitables.push_back(m_xConnection); 
     waitables.push_back(m_globalTimer); 
     Waitable* whatHappened = System::waitOnAll(waitables); 
     switch(whatHappened) { 
      case &m_networkSocket: readAndDispatchNetworkEvent(); break; 
      case &m_xConnection: readAndDispatchGuiEvent(); break; 
      case &m_globalTimer: readAndDispatchTimerEvent(); break; 
     } 
    } 
} 

Co to jest "Waitable"? Cóż, zależy to od systemu. W systemie UNIX nazywane jest "deskryptorem pliku", a "waitOnAll" jest wywołaniem systemowym :: select. Tak zwany vector<Waitable> jest ::fd_set w systemie UNIX, a "whatHappened" jest faktycznie sprawdzane przez FD_ISSET. Rzeczywiste, dające się kelować, uchwyty są pozyskiwane na różne sposoby, na przykład m_xConnection można pobrać z :: XConnectionNumber(). X11 zapewnia również przenośny interfejs API wysokiego poziomu dla tego - :: XNextEvent() - ale gdybyś używał tego, nie byłbyś w stanie czekać na kilka źródeł zdarzeń jednocześnie.

Jak działa blokowanie? "waitOnAll" to syscall, który informuje system operacyjny, aby umieścił twój proces na "liście uśpienia". Oznacza to, że nie masz czasu procesora, dopóki zdarzenie nie wystąpi w jednej z funkcji oczekiwania. Oznacza to, że proces jest bezczynny, zużywając 0% CPU. Kiedy wystąpi zdarzenie, Twój proces krótko zareaguje na niego, a następnie powróci do stanu bezczynności.Aplikacje GUI wydają prawie wszystkie swój czas na biegu jałowym.

Co dzieje się ze wszystkimi cyklami procesora podczas snu? Zależy. Czasami inny proces będzie dla nich przydatny. Jeśli nie, Twój system operacyjny zająłby się zapętleniem procesora lub przełączeniem go w tymczasowy tryb niskiego poboru mocy, itp.

Proszę pytać o dalsze szczegóły!

+0

Jak zaimplementować taki system oczekiwania, aby nie czekać na sygnały systemowe, ale na własne sygnały? – fengshaun

+0

Jak już powiedziałem, twój kod działa tylko w reakcji na zdarzenia. Dlatego jeśli wystrzelisz własne wydarzenie, zrobisz to jako reakcję na pewne wydarzenie systemowe. A potem staje się jasne, że w rzeczywistości nie potrzebujesz systemu zdarzeń dla własnych zdarzeń. Wystarczy zadzwonić bezpośrednio do obsługi! –

+0

Weźmy na przykład sygnał "Przycisk :: kliknięty". Będzie strzelał tylko w odpowiedzi na zdarzenie systemowe (zwolnienie lewego przycisku myszy). Zatem twój kod staje się "wirtualnym pustym przyciskiem Button :: handleLeftRelease (Point) {clicked.invoke();}" bez potrzeby tworzenia wątków lub kolejki zdarzeń lub czegokolwiek. –

11

Generalnie chciałbym to zrobić z jakimś counting semaphore:

  1. Semafor zaczyna się od zera.
  2. Pętla zdarzeń czeka na semaforze.
  3. Zdarzenie (wejścia), semafor jest inkrementowany.
  4. Obsługa zdarzeń odblokowuje i dekrementuje semafor i przetwarza zdarzenie.
  5. Po przetworzeniu wszystkich zdarzeń semafor wynosi zero, a pętla zdarzeń blokuje się ponownie.

Jeśli nie chcesz, aby to skomplikowane, możesz po prostu dodać wywołanie sleep() w pętli while z trywialnie małym czasem uśpienia. To spowoduje, że wątek przetwarzania komunikatów wydłuży czas procesora na inne wątki. Procesor nie będzie już sztywniejszy o 100%, ale nadal jest dość nieekonomiczny.

+0

To brzmi kusząco, będę musiał nauczyć się więcej na temat gwintowania. Dzięki. – fengshaun

+0

, więc potrzebujemy innego wątku, który zajmuje za dużo czasu? –

+0

@FallingFromBed - nie jest zajęte oczekiwanie, ale blokowanie oczekiwania na sepmaphore. Różnica jest ważna, ponieważ czas oczekiwania na blokowanie nie zużyje czasu procesora. –

10

Chciałbym użyć prostej, lekkiej biblioteki komunikatów o nazwie ZeroMQ (http://www.zeromq.org/). Jest to biblioteka typu open source (LGPL). To jest bardzo mała biblioteka; na moim serwerze cały projekt kompiluje się w około 60 sekund.

ZeroMQ znacznie uprości kod sterowany zdarzeniami, ORAZ jest również najbardziej wydajnym rozwiązaniem pod względem wydajności. Komunikacja między wątkami za pomocą ZeroMQ jest znacznie szybsza (pod względem szybkości) niż użycie semaforów lub lokalnych gniazd systemu UNIX. ZeroMQ to także rozwiązanie w 100% przenośne, podczas gdy wszystkie inne rozwiązania wiążą Twój kod z określonym systemem operacyjnym.

+3

Wow, to jest fajne. –

21

Python:

Możesz zajrzeć na realizację Twisted reactor który jest prawdopodobnie najlepszym wdrożenie do pętli zdarzeń w Pythonie. Reaktory w Twisted są implementacjami interfejsu i można określić reaktor typu, który ma być uruchamiany: select, epoll, kqueue (wszystkie oparte na api z wykorzystaniem tych wywołań systemowych), istnieją również reaktory oparte na zestawach narzędzi QT i GTK.

Proste wdrożenie byłoby użyć select:

#echo server that accepts multiple client connections without forking threads 

import select 
import socket 
import sys 

host = '' 
port = 50000 
backlog = 5 
size = 1024 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
server.bind((host,port)) 
server.listen(backlog) 
input = [server,sys.stdin] 
running = 1 

#the eventloop running 
while running: 
    inputready,outputready,exceptready = select.select(input,[],[]) 

    for s in inputready: 

     if s == server: 
      # handle the server socket 
      client, address = server.accept() 
      input.append(client) 

     elif s == sys.stdin: 
      # handle standard input 
      junk = sys.stdin.readline() 
      running = 0 

     else: 
      # handle all other sockets 
      data = s.recv(size) 
      if data: 
       s.send(data) 
      else: 
       s.close() 
       input.remove(s) 
server.close() 
Powiązane problemy