Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

C++ QT - Harmonogram zadań i QTimer

MrOwocowy 30 Aug 2014 18:31 1938 13
  • #1
    MrOwocowy
    Level 9  
    Przy użyciu biblioteki Qt chce stworzyć coś w rodzaju harmonogramu zadać. W skrócie wygląda to tak:

    - Użytkownik klika przycisk DODAJ i otwiera się nowe okno z kilkoma parametrami, ustawia godzinę i zatwierdza.
    - W głównym oknie programu znajduje się QListWidget do którego zostaje dopisany opis zadania wraz z godziną
    - Przy zatwierdzaniu wywołuję funkcje z mojej klasy harmonogram, która posiada tablicę 10 elementów QTimer. Przy użyciu pierwszego elementu tablicy tworzę połączenia z sygnałem QTimer::timeout.
    - Liczona jest ilość sekund między obecną godziną a godziną ustawioną przez użytkownika i uruchamia się timer z określonym czasem.

    Code: cpp
    Log in, to see the code


    Czasami będzie trzeba takie zadanie zatrzymać / usunąć zanim jeszcze się wykona. Obok przycisku dodającego zadania jest przycisk, który te właśnie zadania usuwa.

    Code: cpp
    Log in, to see the code


    Z QListWidget pobierany jest "numer" zadania ( indeks ) i wysyłany do funkcji, która powinna zatrzymać odpowiedni QTimer.

    Code: cpp
    Log in, to see the code


    Problem polega na tym, że licznik nie chce się zatrzymać i pracuje dalej, aż do wysłania sygnału timeout i wykonania operacji, dopiero wtedy się zatrzymuje. Próbowałem już chyba wszystkiego co tylko potrafię. W 90% przypadków program się crashuje, albo tak jak napisałem wyżej QTimer działa dalej, pozostałe 10% przypadków to... sam nie wiem. Czasami coś zrobię i niby działa, ale już przy następnej próbie już nie.

    Liczę na waszą pomoc. Zakładam też, że jest jakiś inny sprytny sposób, aby wykonać taki harmonogram. Jeżeli ktoś ma jakąś wizję to proszę o jej zamieszczenie poniżej.
  • #2
    Dżyszla
    Level 42  
    Ja taki harmonogram robiłem rezygnując jednak z tiemrów, a robiąc wątek i sprawdzając cyklicznie, czy któreś zadanie jest do odpalenia (warto posłużyć się listą cykliczną posortowaną - wtedy wystarczy sprawdzać tylko jeden element.
  • #3
    MrOwocowy
    Level 9  
    Zapomniałem umieścić post w dziale dla początkujących z tego względu, że mam dość ubogą wiedzę na temat programowania. Wynika z tego, że nie zrozumiałem z twojej wypowiedzi ani słowa. Szperając po różnych forach na temat właśnie timer czasami właśnie pada temat wątków, jednak nie wiem jak wygląda to w programowaniu, co z tym mogę zrobić i czy może mi to jakoś pomóc. Dzięki jednak Dżyszla za twoją wypowiedź.
  • Helpful post
    #4
    Dżyszla
    Level 42  
    Wątek to nic innego, jak niezależny blok kodu, który po części żyje własnym życiem. Choć jest uzależniony od procesu (programu), który go stworzył, to dysponuje przede wszystkim własnym czasem procesora. Dzięki temu możesz w nim wykonywać instrukcje w sposób równoległy (niezależny) od reszty programu (notabene, każdy program posiada przynajmniej jeden, tzw. główny, wątek, w ramach którego uruchamia się aplikacja).

    Wniosek jest taki, że tworząc wątek masz:
    1. Większą pewność wykonania zadania o czasie (jeśli główny wątek zostanie zajęty jakimś zadaniem, choćby obsługą kliknięcia, to sygnał wiadomości timera nie zostanie obsłużony natychmiast).
    2. Całkowitą niezależność od wątku głównego, przez co nie blokujesz go działaniem równoległych instrukcji.

    Co jeszcze może pójść nie tak opierając harmonogram na timerach? A no np. zmiana daty podczas działania programu (np. spowodowana aktualizacją ustawień zegara). Jeśli wątek będzie sprawdzał aktualną godzinę z zaplanowaną, to nie przeszkodzi mu taki przeskok zegara. Mała uwaga - dokonując sprawdzenia przez wątek można co prawda wymusić działanie prawie w czasie rzeczywistym, ale to by spowodowało całkowite obciążenie procesora. Dlatego trzeba pogodzić się z pewną niedokładnością np. 10-100ms (w zależności, na ile zgodzisz się dociążyć procesor).

    W praktyce do obsługi wątku często przeznaczone są dedykowane klasy. Ale w najniższym poziomie jest to po prostu bezparametrowa procedura, do której wskaźnik przekazuje się podczas wywołania CreateThread z WinAPI.

    Jak wyglądać może taki algorytm harmonogramu?
    1. Pobieramy listę wszystkich zdarzeń i sortujemy ją wg czasu najbliższego wykonania.
    2. Sprawdzamy, czy aktualny czas jest >= czasowi zdarzenia z początku (końca - w zależności od sortowania) listy.
    3. Jeśli tak, wykonujemy zdarzenie, po czym przesuwamy je na koniec (początek) listy, chyba, że kolejne wykonanie miałoby znaleźć się w innym miejscu ze względu na kolejność (tu duże znaczenie ma projekt samej listy - czy będzie ona w ujęciu np. dnia, tygodnia, czy może niektóre zadania będą opisane "codziennie" a inne "co tydzień").
    4. Wracamy do punktu 2 (pamiętajmy, że jedno wydarzenie zostało zdjęte z listy i teraz już się sprawdza czas z kolejnym elementem).

    Jeśli operacje wykonywane są czasochłonne, a zależy nam, aby nie zablokowały kolejnych - wówczas odpalamy je ponownie w kolejnym, oddzielnym wątku. Jeśli jednak chcemy, aby tylko jedno zadanie było wykonywane na raz (nawet, jeśli harmonogram nakazuje inaczej), wówczas odpalamy w tym właśnie wątku, a kolejne zadanie zostanie wykonane (prawie) natychmiast po zakończeniu poprzedniego, co zapewnia warunek sprawdzający w głównej pętli wątku.

    Niestety, jaśniej już chyba nie będę umiał opisać nie tworząc przy tym obszernego artykułu, więc odsyłam do Internetu, jeśli coś więcej chcesz się dowiedzieć o programowaniu współbieżnym.
  • #5
    MrOwocowy
    Level 9  
    wow... Takiej odpowiedzi się nie spodziewałem, ale jest ona jak najbardziej pomocna i dziękuję za nią. Jednak po przeczytaniu tego małego "artykułu" nasuwa mi się kilka moich wniosków. Taki harmonogram piszę już po raz kolejny, tylko że tym razem używam do tego oddzielnej klasy. W jego "pierwotnej" wersji również używałem timera, jednak wtedy nie obliczałem różnicy w czasie między zaplanowaną godziną a aktualną tylko co sekundę sprawdzałem czy są one sobie równe, jeśli tak to zadanie zostawało wykonywane i timer się zatrzymywał. Wtedy też ilość zadań była ograniczona do 6, a każde zadanie posiadało swój oddzielny timer jak i obiekt QMetaObject::Connection co nie powodowało już problemów z zatrzymaniem zegar przed wykonaniem zadania. Wracając do wniosków, uważam taki sposób za zbyt obciążający procesor. Nawet jeśli moje zadania będę opóźnione o jedną czy nawet dwie sekundy z racji tego, że znajdują się w głównym wątku programu, czy też w jakimś pobocznym, ale równie zapełnionym to nie będzie to problemem. A co do tego jakie operacje są wykonywane to jest to wysyłanie 4 bajtowej ramki danych przez port COM. Zadania czasami będą musiały czekać na wysłanie 1 godzinę, a czasami 10 godzin. Nie przewiduje też powtarzania zadań co dzień czy tydzień. Coś takiego oczywiście mógłbym umieścić w programie, ale nie jest to teraz koniecznie i nie mam już siły myśleć nad kolejnymi błędami. Muszę uporać się z tym opisanym w pierwszym poście. Spróbuję znaleźć może coś jeszcze na własną rękę na temat wątków, ale póki co czekam zamierzam zostać przy timerach, może coś wykombinuję.
  • #6
    Dżyszla
    Level 42  
    Nie znam środowiska, ale nie ma tam przypadkiem czegoś takiego, jak interwał dla timera? Podejrzewam, że start z parametrem powoduje jakiś inny sposób działania i zaklepuje. Jak nie to, to spróbuj odpiąć zdarzenie wtedy, gdy wykonujesz stop, albo wręcz unicestwić cały komponent.
  • #7
    mpier
    Level 28  
    Witam,
    nie wydaje mi się, żeby opieranie się na numerze wiersza było najlepszym pomysłem. Może dodaj do pozycji informację o timerze, który ja obsługuje?
  • #8
    MrOwocowy
    Level 9  
    Interwał jest ale to według mnie nie ma znaczenia. Timer po wysłaniu sygnału timeout, wysyła 4 bajty i zatrzymuje się tak jak powinien. To moje "ręczne" zatrzymanie nie chce działać. Zaalokowałem teraz dynamicznie jeden obiekt typu QTimer o nazwie licznik. Przypisuje mu adres w konstruktorze, ale nadal nic.
    Code: cpp
    Log in, to see the code


    @EDIT

    Jeśli jednak stworzę obiekt poza wszelkimi funkcjami w pliku harmonogram.cpp to wtedy wszystko działa jak trzeba, ale to nie jest rozwiązanie.

    @EDIT2

    Opieranie się o numer wiersza to pierwsze na co wpadłem, nie testowałem tego jeszcze w praktyce więc nie mogę powiedzieć czy to dobry pomysł czy nie. Dopiero jak będę w stanie normalnie obsługiwać jeden timer to wtedy zacznę się bawić w stworzenie 10 elementowej tablicy i testowanie "numeru wiersza". W tym momencie nie widzę w tym czegoś złego jednak może to wynikać z braku mojego doświadczenia.
  • #9
    mpier
    Level 28  
    Musiałeś się pomylić, ciężko powiedzieć bez kodu.
    Numer wiersza nie jest związany z pozycja, tylko z jej pozycją na liście licząc od góry. W ten sposób usuwając pierwszą pozycję, zawsze będziesz usuwał wiersz o numerze 0. Nie wiem czy to poprawnie będzie, ale ja bym stworzył nową klasę dziedziczącą po QListWidgetItem i dodał do niej własne dane, np wskaźnik na QTimer. Możesz też skorzystać z setData,
  • #10
    MrOwocowy
    Level 9  
    Odłóżmy narazie na bok numery wiersza, pozycje, indeksy i inne tego typu rzeczy.Też zakładałem, że z listy przesyłany jest błędy numer, ale raz: nie ma to teraz znaczenia, a dwa: przesyłany jest odpowiedni numer, przynajmniej tak wynika z moich obserwacji. Ten błąd pojawia się nawet kiedy zamiast
    Code: cpp
    Log in, to see the code

    i
    Code: cpp
    Log in, to see the code


    wpiszę

    Code: cpp
    Log in, to see the code

    Code: cpp
    Log in, to see the code


    Nawet kiedy mam w klasie tylko jeden obiekt typu QTimer i to właśnie z nim utworzę połączenie to nie potrafię go zatrzymać. Nic, a nic nie reaguje.

    harmonogram.h
    Code: cpp
    Log in, to see the code


    harmonogram.cpp
    Code: cpp
    Log in, to see the code
  • #11
    mpier
    Level 28  
    Twoje przykłady kompilują się bez problemów? Nie ma ostrzeżeń? Nie jestem c++ programistą, ale u mnie działa:
    Code: cpp
    Log in, to see the code
  • #12
    MrOwocowy
    Level 9  
    Tak, moje przykłady kompilują się bez problemów. Wszystko działa jak powinno poza właśnie przyciskiem do zatrzymania timera. Tzn. sam przycisk reaguje, ale funkcja zatrzymująca nie działa.

    Jednak mam pytanie, w linijce
    Code: cpp
    Log in, to see the code

    W nawiasie kwadratowym mam znak "=", wpisałem go tam tylko dla tego, że znalazłem podobny przykład w internecie i nie wiem tak naprawdę czemu on tam jest i do czego służy, jedyne co przychodzi mi do głowy to to, że może on oznaczać jakiś adres. Pewnie się mylę, więc proszę o sprostowanie.
  • #13
    mpier
    Level 28  
    [=] określa sposób przekazywania zmiennych widocznych, tu jest to przez wartość. Poczytaj o lambdach, w tym niestety nie pomogą za dużo. QTimer działa u mnie tak samo w programie okienkowym, musisz mieć błąd w kodzie. Narysuj proste okienko z labelem i dwoma przyciskami i testuj timery tam, bez całej reszty. Wtedy wklej co napisałeś i będzie można poradzić. Nazwanie zmiennej QTimer nie szkodzi Ci w niczym? Nie masz ostrzeżeń nawet?
  • #14
    MrOwocowy
    Level 9  
    Moje zmienne nie mają nazwy QTimer, napisałem tak tylko dla tego, żeby było wiadomo, co to za obiekt. Gdybym wpisał tam "licznik" to zakładam, że potem ktoś pytałby się jakim typem jest obiekt licznik. Jutro postaram się sprawdzić osobny program z samymi timerami.

    @EDIT

    Udało mi się, a oto prawdopodobny powód i moje rozwiązanie:

    W programie mam 2 okna. Jedno główne do zarządzania całym programem i drugie "poboczne", w którym ustawiam godzinę i inne pierdoły. W oknie głównym jest przycisk "Dodaj", który otwiera okono poboczne, a w nim po wybraniu przez "użytkownika" odpowiadających mu opcji klika on przycisk OK. Powoduje to dwie rzeczy:

    1. wyemitowanie sygnału, który połączony jest z oknem głównym i przesłanie odpowiednich argumentów do funkcji, który tworzy nich prawidłowy QString oraz wyświetla go w QListWidget.
    2. Uruchomienie timera z obliczonym już czasem.
    Code: cpp
    Log in, to see the code


    Natomiast, aby zatrzymać licznik muszę kliknąć przycisk "Usuń", który to znajduje się na oknie głównym.

    Teraz moja prawdopodobna teoria, który wynika z przeczytaniu paru for. Jestem prawie pewien, że oto chodzi, jednak nie mogę tego potwierdzić.

    Okno główne i okno "poboczne" posiadają dwa odrębne wątki. QTimer uruchamiam w wątki okna "pobocznego", a zatrzymuje go już w wątki okna "głównego". Na jakimś forum z tego co pamiętam był temat związany z tym, że QTimer, nie może być zatrzymany z innego wątku. Zamieniłem więc linię
    Code: cpp
    Log in, to see the code
    na wyemitowanie sygnału do okna głównego i to dopiero w nim wywołuję funkcję startującą QTimer. Dzięki temu zabiegowi wszystko działa jak trzeba.

    Teraz zajmę się utworzeniem tablicy i zwiększeniem liczby możliwych zadań. Nie chce jeszcze zamykać tego tematu, bo coś jeszcze pewnie pójdzie nie po mojej myśli.

    Temat do zamknięcia. Dziękuję za pomoc.