2 17 Timery oraz przetwarzanie w jałowym czasie aplikacji (2)


Rozdział 17.
Timery oraz przetwarzanie w jałowym czasie aplikacji


W tym rozdziale:

Ustawianie timera za pomocą komunikatu WM_SETTIMER
Korzystanie ze zwrotnej funkcji timera
Program Clock
Funkcja Onidle() klasy aplikacji
Program OnldleDemo


Skierujemy teraz uwagę ku zagadnieniom związanym z timerami oraz ku przetwarzaniu w jałowym czasie aplikacji. Aż do tej chwili zajmowaliśmy się elementami programu widocznymi dla użytkownika. Jednak z pewnością kiedyś znajdziesz się w sytuacji, kiedy zechcesz, by program wykonywał coś w tle, w sposób jak najmniej zauważalny. Weźmy na przykład funkcję automatycznego zapisu występującą w wielu nowoczesnych aplikacjach. Jeśli aplikacja wstrzymałaby działanie na czas automatycznego zapisu, korzystanie z niej szybko stałoby się dość frustrujące. Dużo lepsze jest przeprowadzanie automatycznego zapisu w tle, bez przeszkadzania użytkownikowi w wykonywaniu podstawowych czynności.
Poznamy dwa mechanizmy Windows umożliwiające wykonywanie zadań w tle. W pierwszej połowie rozdziału zajmiemy się timerami, które mogą "tykać" już co 55 milisekund. Możesz je wykorzystać w wielu rodzajach aplikacji, takich jak zegary, programy kopii zapasowych i wszystkich innych programach wymagających "obudzenia" w określonych momentach.
W drugiej połowie rozdziału odejdziemy od timerów i zajmiemy się przetwarzaniem w czasie jałowym. Do kilku z wielu zastosowań przetwarzania w czasie jałowym należy drukowanie w tle i oczyszczanie pamięci. Pewne zadania, które w 16-bitowych Windows doskonale nadawały się do obsłużenia w czasie jałowym, w Windows 98 lepiej obsługuje się za pomocą działających w tle wątków, jednak czas jałowy wciąż ma swoje zastosowania. W drugim z dwóch demonstracyjnych programów zobaczymy więc, jak działa przetwarzanie w czasie jałowym w aplikacji 32-bitowej.
Timery
Aby korzystać z timerów, musisz znać jedynie dwie funkcje: cwnd: : SetTimer () program ującątimer tak, by "tykał" co zadany czas oraz cwnd:: KiliTimer () zatrzymującą działający timer. W zależności od parametrów przekazanych funkcji SetTimer () timer informuje aplikację o upłynięciu zadanego interwału na dwa sposoby: wysyłając komunikat WM_TIMER oraz wywołując zdefiniowaną przez aplikację funkcję zwrotną.
Metoda korzystająca z komunikatu WM_TIMER jest prostsza, jednak w przypadku zastosowania kilku timerów czasem lepiej jest użyć funkcji zwrotnej. W obu przypadkach timer ma jednak dość niski priorytet i aplikacja jest powiadamiana o "tyknięciu" tylko wtedy, gdy w kolejce nie oczekują inne komunikaty.
Komunikaty timera nigdy nie są gromadzone w kolejce komunikatów, co jest ogromną zaletą. Pomyśl, co mogłoby się stać, gdyby zaprogramowano timer na tykanie co 250 milisekund, lecz wywoływany w wyniku tyknięcia kod wykonywałby się dłużej. Program nie miałby szans opróżnienia kolejki i w związku z tym wykonywałby się bez końca.
Ponieważ Windows nie pozwala na gromadzenie się w kolejce komunikatów pochodzących od timera, zostaje rozwiązany potencjalny problem. W podanym przykładzie, gdy zakończy działanie kod obsługujący komunikat timera, okno otrzyma kolejny komunikat dopiero na końcu następnego 250 milisekundowego cyklu.
Jednak aplikacja Windows nigdy nie powinna poświęcać większej ilości czasu przetwarzaniu pojedynczego komunikatu, lecz powinna w tym celu uruchomić osobny wątek. Jeśli ten warunek nie zostanie spełniony, program przestaje reagować tak szybko jak powinien.
Przygotowanie timera
do wysyłania komunikatów WMJTIMER
Najprostszą metodą ustawienia timera jest wywołanie funkcji SetTimer () z podaniem jedynie identyfikatora timera oraz czasu pomiędzy "tyknięciami", a następnie obsłużenie komunikatów WM_TIMER w funkcji onTimer () okna. Identyfikator timera musi być wartością niezerową. Gdy po otrzymaniu komunikatu WM_TIMER jest wywoływana funkcja obsługi OnTimer (), jako jej argument jest przekazywany właśnie identyfikator timera zgłaszającego komunikat. Jeśli skorzystasz tylko z jednego timera, identyfikator prawdopodobnie nie będzie Cię interesował, gdyż wszystkie komunikaty WM_TIMER będą pochodzić z jednego tylko źródła. W aplikacjach korzystających z dwóch i więcej timerów identyfikator timera może być jednak bardzo przydatny.
Wartość przekazywana funkcji SetTimer o określa odstęp między kolejnymi komunikatami WM_TIMER, wyrażony w tysięcznych częściach sekundy. Przekazywana wartość może należeć do przedziału od l do największej całkowitej liczby 32-bitowej: 232-l, co odpowiada okresowi nieco dłuższemu niż 49,5 dnia. Poniższy kod alokuje timer, przypisuje mu identyfikator l i programuje go, aby co każde 500 milisekund wysyłał do okna komunikat WM_TIMER. Wartość NULL przekazana jako trzeci parametr powoduje, że timer wysyła komunikat WM_TIMER, lecz nie wywołuje funkcji zwrotnej:
SetTimer( l, 500, NULL);
Choć zaprogramowany interwał wynosi 500 milisekund, okno w rzeczywistości będzie otrzymywać komunikat WM_TIMER co 550 milisekund, gdyż sprzętowy zegar na podstawie którego generowane są komunikaty timera, "tyka" co 54,9 milisekundy . W efekcie Windows zaokrągla wartość przekazywaną funkcji SetTimer () do następnej wielokrotności 55 milisekund. Tak więc obie poniższe linie kodu programuj ą timer tak, że w obu przypadkach wysyła komunikat WM_TIMER co około 55 milisekund:
SetTimer(l,l, NULL ); SetTimer(l,50, NULL);



Jeśli, tak jak w następnej linii, przekażesz wartość 60 ms, komunikaty WM_TIMER będą generowane co 110 milisekund:
SetTimer( l,60, NULL);

Niestety, timer to nie Omega. Jeśli piszesz aplikację imitującą zegarek i zaprogramujesz timer, by "tykał" co 1000 milisekund, raczej nie oczekuj, że w ciągu minuty wystąpi dokładnie 60 komunikatów WM_TIMER. Zamiast tego powinieneś w momencie otrzymania komunikatu sprawdzić bieżący czas i na tej podstawie zaktualizować wartość wyświetlaną przez swoją aplikację. W ten sposób, nawet w wypadku zakłócenia generowania komunikatów timera, wyświetlany czas pozostanie poprawny.
Jeśli piszesz aplikację wymagającą precyzyjnego odmierzania czasu, zamiast konwencjonalnego timera możesz użyć timera dla multimediów, programując go na "tykanie" z dokładnością większą niż jedna milisekunda. Timery multimedialne są niesamowicie precyzyjne i idealnie nadają się dla specjalistycznych aplikacji, takich jak sekwencery MIDI, należy jednak mieć na uwadze dużo większy narzut związany z ich obsługą i duży wpływ na wydajność innych programów działających w systemie.
Wartością zwracaną przez funkcję SetTimer () jest identyfikator timera, jeśli wywołanie się powiodło, lub wartość O w przeciwnym przypadku. W wersjach Windows wcześniejszych niż Windows 95 timery stanowiły wspólny zasób systemowy o ograniczonej liczbie timerów. Poczynając od Windows 95, ilość timerów, które mogą zostać obsłużone przez system, zależy wyłącznie od rozmiaru dostępnej pamięci. Tak więc nie powinny się już zdarzać problemy związane z nieprzydzieleniem timera, jednak w dalszym ciągu dobrze jest sprawdzić zwracaną wartość, choćby po to, by upewnić się, że systemowi nie brakuje zasobów. Zwracany przez funkcję SetTimer () identyfikator timera odpowiada identyfikatorowi przekazanemu jako pierwszy parametr funkcji, chyba że przekazałeś wartość zero. W takim przypadku funkcja SetTimer () zwróci identyfikator równy 1. Wywołanie funkcji SetTimer () powiedzie się także wtedy, gdy kilku oknom przypiszesz ten sam identyfikator timera. W takim przypadku wszystkie te timery będą generować dla swoich okien komunikaty WM_TIMER zawierające ten sam identyfikator.
Funkcja SetTimer () może zostać użyta także do zmiany przypisanej już wartości okresu. Jeśli timer l już istnieje, poniższa instrukcja przeprogramowuje go tak, by tykał co 1000 milisekund:
UWAGA: W Windows NT timer "tyka" dużo precyzyjniej, zwykle z dokładnością do około 10 milisekund.
SetTimer( l, 1000, NULL );
Przeprogramowanie timera powoduje wyzerowanie jego wewnętrznego zegara, tak że następny komunikat nie nadejdzie aż do upłynięcia zadanego okresu czasu.
Tworzenie timerów
Poniższa procedura podsumowuje kroki związane z tworzeniem i wykorzystaniem timera:
1. Na liście Messagess odszukaj komunikaty WM_CREATE oraz WM_TIMER, po czym stwórz dla nich funkcje obsługi.
2. W funkcji OnCreate () odpal timer: SetTimer( l, 1000, NULL );
3. W funkcji obsługi onTimer () wstaw swój okresowo wywoływany kod.
4. Skompiluj i uruchom program.
Ważne jest, by pamiętać, że komunikaty WM_TIMER nie są przetwarzane asynchronicznie względem innych komunikatów. Komunikat WM_TIMER nigdy nie przerwie obsługi innego komunikatu WMJTIMER w tym samym wątku wykonywania, nie przerwie także obsługi żadnego innego komunikatu. Jak już wspominaliśmy, komunikaty WMJTIMER mają bardzo niski priorytet.
Komunikaty WMJTIMER, podobnie jak inne komunikaty, oczekują w kolejce na przetworzenie i rozprowadzenie przez pętlę komunikatów. Ten niski priorytet rozszerza się nawet na samą funkcję OnTimer (). Na przykład, jeśli dwie funkcje modyfikują tę samą zmienną składową, Windows gwarantuje, że kod w funkcji OnTimer () nie zmodyfikuje jej przed zakończeniem działania tej drugiej funkcji. Oczywiście, nie ma takiej gwarancji, jeśli obie funkcje występują w różnych wątkach.
Przygotowanie timera do wywoływania funkcji zwrotnej
Timery nie muszą generować komunikatów WM_TIMER. Jeśli wolisz, możesz tak skonfigurować timer, aby zamiast wysyłania komunikatu wywoływał zdefiniowaną w aplikacji funkcję zwrotną. Ta metoda jest zwykle stosowana w aplikacjach korzystających z wielu timerów, dzięki czemu każdy z nich może być w prosty sposób niezależnie obsłużony.
Wśród programistów Windows pokutuje błędne przekonanie, że funkcja zwrotna timera jest wywoływana z większym priorytetem niż funkcja obsługi komunikatu timera, gdyż jest wywoływana bezpośrednio i nie wiąże się z umieszczaniem komunikatu w kolejce. W rzeczywistości, od momentu wywołania funkcji DispatchMessage (), komunikaty i funkcje zwrotne są traktowane w identyczny sposób.
Gdy następuje "tyknięcie" timera, Windows ustawia w kolejce komunikatów znacznik wskazujący, że komunikat timera lub funkcja zwrotna oczekują obsłużenia. Gdy funkcja GetMessage () stwierdzi, że kolejka komunikatów jest pusta i że żadne z okien nie wymaga odrysowania, sprawdza stan znacznika timera. Jeśli ten znacznik jest ustawio-
ny, funkcja GetMessageO buduje komunikat WMJTIMER wysyłany następnie przez funkcję DispatchMessage ().
Jeśli timer generujący komunikat rzeczywiście ma generować komunikaty WM_TIMER, do procedury okna jest kierowany właśnie taki komunikat. Jeśli jednak zamiast tego została zarejestrowana funkcja zwrotna, wywołuje ją funkcja DispatchMessage (). Tak więc funkcja zwrotna nie posiada absolutnie żadnego większego priorytetu niż komunikaty WMJTIMER. Jedyna korzyść wiąże się z nieco niższym narzutem związanym z wywołaniem funkcji zwrotnej, gdyż nie ma potrzeby sortowania identyfikatorów komunikatów w mapie komunikatów lub procedurze okna, jednak ta różnica jest praktycznie niezauważalna. W praktyce okazuje się, że komunikaty WMJTIMER są generowane z taką samą częstotliwością z jaką wywoływane są funkcje zwrotne.
Aby przygotować timer wywołujący funkcję zwrotną, jako trzeci parametr podaj nazwę tej funkcji:
SetTimerd, 1000, TimerProc); Procedura zwrotna, o nazwie TimerProc (), powinna być zadeklarowana następująco:
void CALLBACK TimerProc( HWND hWnd, UINT nMsg, UINT nTimerlD, DWORD dwTime );
Parametr hWnd zawiera uchwyt okna, nMsg zawiera identyfikator komunikatu, WM_TIMER, nTimerlD zawiera identyfikator timera, zaś dwTime określa ilość milisekund, jaka upłynęła od uruchomienia Windows. Funkcja zwrotna powinna być zadeklarowana jako statyczna funkcja składowa klasy okna lub powinny zostać podjęte inne kroki w celu zabezpieczenia tego wskaźnika przed przekazaniem na stosie.
Jedną z wad, z jakimi wiąże się użycie statycznej funkcji składowej jako funkcji zwrotnej timera, jest to, że procedura timera nie otrzymuje zdefiniowanej przez użytkownika wartości l Param, przekazywanej w przypadku innych funkcji zwrotnych. W funkcji timera musisz sam stworzyć wskaźnik do obiektu okna, jeśli chcesz odwołać się do nie-statycznych funkcji i zmiennych składowych. Na szczęście, w poniższy sposób możesz uzyskać wskaźnik do głównego okna swojej aplikacji MFC:
CMainWindow *pMainWnd = (CMainWindow*) AfxGetMainWnd();
Rzutowanie otrzymanego wskaźnika przed odwołaniem się do funkcji i zmiennych składowych jest koniecznością, gdyż funkcja Af xGetMainWnd () zwraca wskaźnik do ogólnej klasy cwnd. Gdy w ten sposób zostanie zainicjowany wskaźnik pMainWnd, funkcja TimerProc(), także należąca do klasy CMainWindow, będzie mogła odwoływać się do niestatycznych funkcji i zmiennych tej klasy, tak jakby także była funkcjąniestatyczną.
Jako uzupełnienie funkcji CWnd: :SetTimer() występuje funkcja CWnd: iKillTimer (), która zatrzymuje działanie timera i wysyłanie komunikatów WM_TIMER lub wywoływanie funkcji zwrotnych. Poniższa instrukcja zwalnia timer o identyfikatorze l:
KillTimer( l );
Dobrym miejscem niszczenia timera utworzonego w funkcji OnCreate () jest funkcja OnClose () lub onDestroy () okna. Jeśli aplikacja nie zniszczy timera przed zakończeniem swojego działania, Windows uczyni to za nią po zakończeniu procesu.
Program Clock
Pierwszy z dwóch programów demonstracyjnych ilustruje sposób tworzenia z użyciem timera aplikacji udającej zegarek. W funkcji setTimer () jako trzeci parametr przekazujemy wartość NULL, w związku z czym do okna będą wysyłane komunikaty WM_TIMER.
Ten program umożliwia wyświetlanie zegarka analogowego lub cyfrowego. Aby zmienić sposób wyświetlania, po prostu wybierz odpowiednią pozycję w menu Rodzaj. Wygląd programu przedstawiono na rysunku 17.1, zaś fragmenty kodu zamieszczono na listingu 17. l.
Clock
Położenie na płytce CD-ROM: Rozdzl7\Clock
Nazwa programu: Clock.EXE
Moduły kodu źródłowego w tekście: ClockVie-w.cpp
Listing 17.1. Implementacja klasy CClockYiew
// ClockView. cpp : implementation of // the CClockView class
//
/////////////////////////////////////
// CClockView construction/destruction CClockView: : CClockYiew ()
{
// Na początek zegarek analogowy. m_nClockType = CLOCK ANALOG;
}
CClockView::-CClockView(
}
//////////////////////////////////////////////////////////////
//CClockView DRAWING
void CClockView: :OnDraw(CDC* pDC) {
CClockDoc* pDoc = GetDocument ( ) ;
ASSERT_VALID(pDoc) ;
// Pobranie prostokąta obszaru roboczego // w celu dynamicznego dopasowania // zegarka do okna. RECT Rect; GetClientRect ( &Rect ) ;
// Dla ułatwienia obliczeń pobranie // wsp. x i y środka okna. int nCenterX = Rect.right / 2; int nCenterY = Rect.bottom / 2;
// Odczytanie bieżącego czasu systemu. CTime Time = CTime : : GetCurrentTime ( ) ;
CString strDigits; int i, x, y; CSize size;
switch( m_nClockType ){ case CLOCK_ANALOG : {
// Żółte pióro do rysowania elipsy. CPen Pen( PS_SOLID, 5, RGB ( 255, 255, O ) );
// Wybranie nowego pióra i zapamiętanie starego, CPen *p01dPen = pDC->SelectObject ( &Pen ) ;
// Rysowanie tarczy zegarka. pDG->Ellipse ( 5, 5, Rect.right - 5, Rect.bottom - 5 ) ;
double Radians;
// Kolorem tekstu będzie czerwony. pDC->SetTextColor ( RGB ( 255, O, O ) ) ;
for ( i=l; i<=12;I++) {

// Formatowanie obiektu CString
// w celu wyświetlenia godziny na tarczy.
strDigits. Format ( "%d", i ) ;
// Pobranie rozmiaru tekstu w celu // rozmieszczenia go w wybranym // punkcie. size =
pDC->GetTextExtent ( strDigits,
strDigits. GetLength () ) ;
// Obliczenie ilości radianów dla
// danej godziny.
Radians = (double) i * 6.28 / 12.0;
// Obliczenie wsp. x.
// Rozmiaru tekstu użyjemy do
// wyśrodkowania go wzgl. punktu.
x = nCenterK -
( size.cx / 2 ) +
(int) ( (double) ( nCenterX - 20 )* sin( Radians ) );
// Obliczenie wsp. y.
// Rozmiaru tekstu użyjemy do
// wyśrodkowania go wzgl. punktu.
y = nCenterY -
( size.cy / 2 ) -
(int) ( (double) ( nCenterY - 20 ) cos( Radians ) );
// Rysowanie tekstu. pDC->TextOut( x, y, strDigits ) ;
}
// Obliczenie ilości radianów dla
// wskazówki godzinowej.
Radians = (double) Time.GetHour() +
(double) Time.GetMinute() / 60.0 + (double) Time.GetSecondO / 3600.0;
Radians *= 6.28 / 12.0;
// Stworzenie pióra dla wskazówki godzinowej.
// Szerokość pióra to pięć pikseli, zaś
// kolorem będzie zieleń.
CPen HourPen( PS_SOLID, 5, RGB( O, 255, O ) );
// Wybranie nowego pióra w kontekście // urządzenia. pDC->SelectObject( sHourPen ) ;
// Przejście na środek tarczy, po czym // narysowanie wskazówki godzinowej. pDC->MoveTo( nCenterK, nCenterY ); pDC->LineTo(
nCenterK + (int) ( (double) ( nCenterK /3*
sin( Radians ) ),
nCenterY - (int) ( (double) ( nCenterY /3* cos( Radians ) ) ) ;
// Obliczenie ilości radianów dla // wskazówki minutowej. Radians = (double) Time.GetMinute() + (double) Time.GetSecondO / 60.0; Radians *= 6.28 / 60.0;
// Stworzenie pióra dla wskazówki minutowej.
// Szerokość pióra to trzy piksele, zaś
// kolorem będzie błękit.
CPen MinutePen( PS_SOLID, 3, RGB( O, O, 255 ));
// Wybranie nowego pióra w kontekście // urządzenia. pDC->SelectObject( SMinutePen );
// Przejście na środek tarczy, po czym // narysowanie wskazówki minutowej. pDC->MoveTo( nCenterX, nCenterY ); pDC->LineTo(
nCenterX + (int) ( (double) (
( nCenterK * 2 ) / 3 ) * sin( Radians)),
nCenterY - (int) ((double)( ( nCenterY * 2) / 3 )* COS( Radians)));



// Obliczenie ilości radianów dla // wskazówki sekundowej. Radians = (double) Time.GetSecond(); Radians *= 6.28 / 60.0;
// Stworzenie pióra dla wskazówki sekundowej.
// Szerokość pióra to jeden piksel, zaś
// kolorem będzie cyjan.
CPen SecondPent PS_SOLID, l, RGB( O, 255, 255));
// Wybranie nowego pióra w kontekście // urządzenia. pDC->SelectObject( &SecondPen );
// Przejście na środek tarczy, po czym // narysowanie wskazówki minutowej. pDC->MoveTo( nCenterX, nCenterY ); pDC->LineTo(
nCenterX + (int) ( (double) (
( nCenterK * 4 ) / 5 ) * sin( Radians)),
nCenterY - (int) ((double)( ( nCenterY * 4) / 5 )* COS( Radians)));

// Wybranie starego pióra w kontekście urządzenia, pDC->SelectObject( pOldPen ); }
break; case CLOCK DIGITAL:
// Zaczniemy od stworzenia napisu // reprezentującego cyfrowy czas. strDigits.Format( "%d:%02d:%02d",
Time.GetHour(),
Time.GetMinute(),
Time.GetSecond() );
CFont Font, *p01dFont; int nWidth = 100; int nHeight = 140;
do{
// Być może wcześniej stworzyliśmy // czcionkę w obiekcie CFont . Tutaj // upewniamy się, że została usunięta. if ( Font.GetSafeHandle () != NULL ) Font .DeleteObject () ;
// Utworzenie czcionki Times New Roman
// z większością domyślnych ustawień.
Font .CreateFont ( nHeight, nWidth, O, O, FW_DONTCARE, O, O, O, ANSIJZHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS , DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Times New Roman" ) ;
// Wybranie nowej czcionki w kontekście // urządzenia i zapamiętanie starej . pOldFont = pDC->SelectObject ( &Font );
// Pobranie rozmiarów tekstu w celu
// sprawdzenia, czy czcionka nie jest zbyt duża.
size =
pDC->GetTextExtent ( strDigits, strDigits .GetLength ( ) ) ;
// Wybranie starej czcionki w kontekście urządzenia, pDC->SelectObject ( pOldFont ) ;
// Jeśli tekst jest zbyt szeroki, zmniejszenie // szerokości czcionki. if ( size. ex > Rect.right ) nWidth -= 5;
// Jeśli tekst jest za wysoki, zmniejszenie // wysokości czcionki. if ( size.cy > Rect.bottom ) nHeight -= 5;
} while ( size. ex > Rect.right | | size.cy > Rect.bottom ) ;
// Wybranie w kontekście urządzenia ostatecznej // czcionki i zapamiętanie starej . pOldFont = pDC->SelectObject ( &Font );
// Rysowanie tekstu.
pDC->TextOut ( nCenterK - ( size. ex / 2 ),
nCenterY - ( size.cy / 2 ),
strDigits ) ;
// Wybranie starej czcionki w kontekście urządzenia. pDC->SelectObject ( pOldFont ); }
break;
}
}
////////////////////////////////////////////////////////////////////
// CClockView message handlers
int CClockView::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
if (CYiew::OnCreate(IpCreateStruct) == -1)
return -1;
// Odpalenie timera. SetTimer( l, 1000, NULL );
return 0; }
void CClockView::OnTimer(UINT nIDEvent)
{
// Timer po prostu powoduje odrysowanie okna. // Aktualny czas zostanie pobrany w // funkcji OnDraw(). InvalidateRect( NULL, TRUE ); UpdateWindow();
CView::OnTimer(nIDEvent);
}
void CClockYiew: :OnTypeAnalog ()
{
// Wybranie zegara analogowego // i odświeżenie okna. m_nClockType = CLOCK_ANALOG; InvalidateRect ( NULL, TRUE ) ; UpdateWindow { ) ;
}
void CClockYiew: :OnUpdateTypeAnalog (CCmdUI* pCmdUi;
{
// Ustawienie znaczka przy pozycji zegarka
// analogowego.
pCmdUI->SetCheck( m_nClockType == CLOCK ANALOG);
}
void CClockYiew::OnTypeDigital()
{
// Wybranie zegara cyfrowego // i odświeżenie okna. m_nClockType = CLOCK_DIGITAL; InvalidateRect( NULL, TRUE ); UpdateWindow();
}
void CClockYiew::OnUpdateTypeDigital(CCmdUI* pCmdUI) {
// Ustawienie znaczka przy pozycji zegarka
// cyfrowego
pCmdUI->SetCheck( m_nClockType == CLOCK_DIGITAL );
}
Przetwarzanie
w jałowym czasie aplikacji
Odkładając na moment timery, masz do dyspozycji jeszcze jeden mechanizm służący do wykonywania zadań o niskim priorytecie. MFC udostępnia tak zwaną pętlę jałową (ang. idle hop}. Program MFC przechodzi do pętli jałowej za każdym razem, gdy kolejka komunikatów staje się pusta. W tej pętli MFC zajmuje się ogólnymi porządkami i do której możesz wstawić swój kod, wykonywany gdy aplikacja oczekuje bezczynnie.
Jeśli bez zwalniania aplikacji mógłbyś rejestrować wywołania funkcji onidie (), okazałoby się, że przy pustej kolejce komunikatów ta funkcja jest wywoływana około stu razy na sekundę. Jednak nawet przy pozornie tak dużej ilości jałowego czasu, ma on - jak wszystko inne w życiu - także swoje ograniczenia. Jak się przekonasz, kod wykonywany w funkcji onidie () wpływa na zdolność aplikacji do obsługi kolejki komunikatów. Oznacza to, że w celu uniknięcia stworzenia "ślamazarnej" aplikacji, kod wykonywany w czasie jałowym musi działać bardzo krótko.
Aplikacja może samodzielnie zajmować się przetwarzaniem w czasie jałowym, po przesłonięciu wirtualnej funkcji onidleO obiektu aplikacji. Funkcja OnidleO jest zadeklarowana następująco:
virtual BOOL Onidie( LONG 1Count );
Parametr icount to wartość 32-bitowa określająca, ile razy funkcja została wywołana od czasu przetworzenia ostatniego komunikatu z kolejki. Licznik zwiększa swą wartość aż do momentu, gdy w pętli komunikatów w funkcji cwinThread: :Run() zostanie wywołana funkcja PumpMessage () w celu pobrania i rozesłania kolejnego komunikatu. W tym momencie licznik jest zerowany. Komunikaty myszy, a także komunikaty WM_PAINT oraz WM_SYSTIMER nie powodują wyzerowania licznika icount. W związku z tym parametr icount może być użyty do przybliżonego mierzenia czasu, przez jaki aplikacja znajduje się w stanie jałowym.
Jeśli masz dwa zadania do wykonania w tle, jedno o wysokim priorytecie, zaś drugie o niskim, możesz użyć parametru icount do określenia, kiedy należy wykonać każde z zadań. Na przykład, zadanie o wysokim priorytecie może być wykonywane, gdy icount osiągnie wielokrotność 10, zaś zadanie o niskim priorytecie będzie wykonywane, gdy icount osiągnie wielokrotność 100 lub nawet 1000.
Zadanie o wysokim priorytecie wywoływane, gdy iCount osiągnie 10 będzie wykonywane stosunkowo często, gdyż licznik zwykle osiąga lub przekracza 10 nawet wtedy gdy pętla komunikatów jest stosunkowo zajęta. Przetwarzanie w czasie jałowym powinno trwać jak najkrócej, gdyż ruch komunikatów jest wstrzymywany aż do chwili powrotu z funkcji Onidle ().
Wartość zwracana przez funkcję omdle () określa, czy ma ona być wywołana ponownie. Jeśli funkcja zwróci wartość różną od zera, to jeśli kolejka komunikatów będzie pusta, funkcja zostanie wywołana ponownie. Jeśli jednak zostanie zwrócona wartość zerowa, dalsze wywołania funkcji Onidle () zostaną wstrzymane aż do opuszczenia kolejki komunikatów przez następne przetworzone komunikaty i ponownego przejścia do stanu jałowego.
Ten mechanizm wykorzystuje w funkcji cwinThread: :Run() znacznik bidle, który początkowo jest ustawiany na TRUE, lecz gdy funkcja omdle () zwróci wartość zero, jest zmieniany na FALSE. Pętla while wywołująca funkcję OnidleO sprawdza stan znacznika bidle na początku każdej iteracji i jeśli ma on wartość FALSE, kończy pętlę i przechodzi dalej. Gdy w kolejce pojawi się kolejny komunikat i zostanie wywołana funkcja PumpMessage () w celu przetworzenia go i wysłania, znacznik bidle jest ponownie ustawiany na TRUE.
Z praktycznego punktu widzenia, możesz zaoszczędzić kilka taktów procesora, jeśli po zakończeniu działania w tle Twoja funkcja OnidleO zwróci wartość FALSE, dzięki czemu nie będzie wywoływana aż do pojawienia się następnego komunikatu. Pamiętaj jednak, by nie zwracać wartości FALSE aż do chwili, kiedy MFC zakończy swoje wewnętrzne porządki, gdyż może to zaowocować błędnym działaniem aplikacji. Na szczęście istnieje prosty sposób zabezpieczenia się przed taką sytuacją; podamy go za moment.
Kardynalną zasadą przy korzystaniu z funkcji Onidle () jest wywoływanie w funkcji przesłoniętej funkcji omdle () klasy podstawowej. Poniższa przesłonięta funkcja omdle () ilustruje właściwą technikę. Najpierw jest wywoływana funkcja Onidle () klasy bazowej, a dopiero gdy nastąpi powrót, aplikacja przystępuje do własnego przetwarzania w czasie jałowym.
BOOL CMyApp::Onidle( LONG 1Count ) {
CWinApp: :Onidle( 1Count ) ;
DoIdleWorl(); // Przetwarzanie w czasie jałowym
return TRUE;
}
Jeszcze lepszym rozwiązaniem jest nadanie nieco większego priorytetu funkcji obsługi Onidle () biblioteki MFC przez odczekanie z rozpoczęciem własnego przetwarzania, aż licznik ICount osiągnie wartość większą niż 2:
BOOL CMyApp::Onidle( LONG 1Count )
{
CWinApp::Onidle( 1Count ); if( 1Count > 2 )
DoIdleWorl(); // Przetwarzanie w czasie jałowym return TRUE;
}
Biblioteka MFC wykonuje swoje przetwarzanie, gdy ICount wynosi O lub 1. Możesz sam sprawdzić, co jest wykonywane, zaglądając do kodu źródłowego funkcji cwin-Thread: :Onidle () w pliku Thrdcore.cpp oraz funkcji CWinApp: :0nidle() w pliku Appcore.cpp.
Ponieważ funkcje Onidle () przedstawione w poprzednich przykładach zawsze zwracają wartość TRUE, będą one wywoływane cały czas, także wtedy gdy zarówno MFC, jak i aplikacja zakończą wykonywanie swoich zadań w czasie jałowym. Poniższa wersja funkcji omdle o minimalizuje marnotrawstwo cykli procesora, zwracając wartość FALSE w momencie zakończenia zadania wykonywanego w czasie jałowym aplikacji:
BOOL CMyApp::Onidle( LONG 1Count )
{
CWinApp::Onidle( 1Count ); BOOL bContinue = TRUE; if ( 1Count > 2) {
DoIdleWorl(); // Przetwarzanie w czasie jałowym
bContinue = FALSE;
}
return (bContinue);
}
Z faktu, że własne przetwarzanie czasu jałowego aplikacji zaczyna się dopiero, gdy licznik icount osiągnie wartość 2 . wynika, że MFC będzie miało dość czasu na wykonanie własnych zadań, zanim funkcja zwróci wartość FALSE.
Bardzo ważne jest, aby zdawać sobie sprawę, że każdy kod wykonywany wewnątrz jałowej pętli wpływa na zdolność aplikacji do rozprowadzania komunikatów. Kod działający wewnątrz tej pętli powinien działać bardzo szybko. Jeśli trzeba, rozbij zadanie na mniejsze, łatwiejsze do przetworzenia fragmenty i wykonuj je w kolejnych wywołaniach funkcji Onidle ( ) . Poniższa funkcja Onidle ( } rozpoczyna działanie, gdy ICount osiągnie wartość 2 i kontynuuje je aż do momentu, w którym funkcja DoidieWorko zwróci wartość FALSE wskazującą, że już wszystko zostało zrobione:
BOOL CMyApp: : Onidle ( LONG 1Count )
CWinApp: : Onidle ( 1Count) BOOL bContinue = TRUE; if ( 1Count > 2 )
bContinue = DoIdleWorl();// Przetwarzanie w czasie jałowym
return bContinue;
}
Program OnldleDemo
Program Onldledemo, którego fragmenty zostały przedstawione na listingu 17.2, pokazuje, w jaki sposób przesłonić i wykorzystać funkcję Onidle o . Przedstawia wartości numeryczne parametru 1Count oraz czas, jaki upłynął od wyzerowania tego licznika. Wygląd programu przedstawiono na rysunku 17.2.
Program rysuje także pasek stanowiący graficzny wskaźnik ilości czasu, jaką program spędza w funkcji Onidie ( ) . Poruszając myszką lub wykonując różne operacje związane z oknem, na przykład zmieniając jego rozmiary, powodujesz, że pasek się kurczy. Oznacza to, że komunikaty oczekujące w kolejce redukują ilość czasu, jaki program może poświęcić wykonywaniu funkcji omdle ( ) .
OnldleDemo
Położenie na płytce CD-ROM: OnldleDemo Moduły kodu źródłowego w tekście: OnldleDemo. cpp
Listing 17.2. Fragmenty programu OnldleDemo __________________________
// OnldleDemo . cpp : Defines the class behaviors // for the application.
//
//////////////////////////////////////////////////
// COnIdleDemoApp commands
DWORD dwTotal = O, dwldle = O, dwFirst, dwLast;
BOOL COnldleDemoApp: rOnldle (LONG 1Count) {
CWinApp: :0nldle (1Count) ;
// Jeśli dwTotal == O, to oznacza pierwszy raz // i musimy zarejestrować dwFirst. if( dwTotal == O )
dwFirst = GetTickCount () ;
// Pobieramy bieżące tyknięcie zegara. DWORD dwTemp = GetTickCount ();
// Obliczamy dwTotal, czyli całkowitą ilość tyknięć zegara // od pierwszego razu w funkcji Onldle(). Dodajemy l, gdyż l/ za pierwszym razem dwTotal wynosi zero i spowodowałoby to // dzielenie przez zero. Ta zmiana nie wpłynie w większym // stopniu na wyniki, gdyż będziemy mieli do czynienia // z dość dużymi liczbami. dwTotal = dwTemp - dwFirst + 1;
// If 1Count != O to oznacza, że nie musieliśmy
// pominąć przetwarzania w czasie jałowym. To znaczy, że
// musimy dodać do dwldle czas, jaki upłynął od pierwszego
// razu. If 1Count == O, to oznacza, że aplikacja przetworzyła
// jakieś komunikaty i nie weszła do funkcji Onldle(). Z tego
// powodu w tym przypadku nie dodajemy czasu, gdyż został on
// zużyty na przetworzenie komunikatu.
if( 1Count != O )
dwldle += ( dwTemp - dwLast );
// Zarejestrowanie w dwLast czasu rozpoczęcia obliczeń. dwLast = dwTemp;
// Potrzebujemy kontekstu urządzenia, więc odwołamy się do // klasy CMainFrame, a następnie COnIdleDemoView. Korzystając // z niej, stworzymy obiekt CClientDC do rysowania. CMainFrame *pFrame =
(CMainFrame *) AfxGetMainWnd(); COnIdleDemoView *pView =
(COnldleDemoView *) pFrame->GetActiveView(}; CClientDC ClientDC( pView );
// Obliczenie procentu i zachowanie go w zmiennej
// lokalnej, aby nie trzeba go było obliczać ponownie.
int nPercent = dwldle * 100 / dwTotal;
// Formatowanie łańcucha i wyrysowanie go w oknie.
CString strlnfo;
strlnfo.Format( "Czas jałowy:%ld Czas całkowity:%ld, %%:%d dwldle, dwTotal, nPercent ); ClientDC.TextOut( 10, 10, strlnfo );
// Pobranie prostokąta obszaru roboczego widoku.
// Przeskalujemy graf, tak aby dobrze pasował do okna,
// bez względu na jego rozmiary.
RECT Rect;
pView->GetClientRect( &Rect );
// Zmniejszymy graf z każdej strony, aby nie dochodził
// do krawędzi okna i aby znajdował się tuż pod tekstem.
// Długość grafu obliczamy jako otrzymany procent czasu
// jałowego.
Rect.left += 10;
Rect.top += 40;
int nRight = Rect.right - 10;
Rect.bottom = Rect.top + 20;
int nWidth = nRight - Rect.left;
nWidth = nWidth * nPercent / 100;
Rect.right = Rect.left + nWidth;
// Rysowanie niebieskiego prostokąta wskazującego // ilość czasu spędzonego w funkcji OnIdleU . CBrush BlueBrush{ RGB( O, O, 255 ) ); ClientDC.FillRect( &Rect, SBlueBrush );
// Rysowanie białego prostokąta na prawo od
// niebieskiego w celu wymazania poprzedniej
// wartości paska.
Rect.left = Rect.right;
Rect.right = nRight;
CBrush WhiteBrush( RGB( 255, 255, 255 ) ) ;
ClientDC.FillRect( &Rect, SWhiteBrush );
return( TRUE );
}
Podsumowanie
Poznaliśmy dwa bardzo użyteczne mechanizmy Windows: timery oraz przetwarzanie w czasie jałowym. Z ich pomocą możesz tworzyć aplikacje cały czas reagujące na polecenia użytkownika, lecz jednocześnie wykonujące w tle swoje zadanie. Są one tak popularne, że któregoś z nich będziesz używał w prawie każdej swojej aplikacji.
Zarządzanie pamięcią Dokumenty, widoki i SDI Układ aplikacji MDI Wydruk i podgląd
wydruku MFC i elementy
zaawansowanego
interfejsu użytkownika Paski stanu i paski
narzędzi Kontrolka widoku drzewa
i kontrolka widoku listy Wątki

Wyszukiwarka

Podobne podstrony:
17 Sporządzanie oraz ekspedycja potraw z kasz i mąki
Badanie mleka, mleka w proszku oraz przetworów mlecznych
Ćw 2 Badanie i wzorcowanie manometrów oraz przetworników ciśnienia
PHP i Oracle Tworzenie aplikacji webowych od przetwarzania danych po Ajaksa
Instrukcja bhp w chłodniach oraz magazynach mięsa i przetworów mięsnych
Podstawy ASP NET 2 0 – tworzenie stron WWW oraz aplikacji Web
Chłodzenie, magazynowanie oraz transport surowców i przetworow mięsnych
Czynnosci operatora przed rozpoczeciem i zakonczeniem pracy oraz w czasie pracy
17 w sprawie warunków technicznych, jakim powinny odpowiadać strzelnice garnizonowe oraz ich usytuow

więcej podobnych podstron