Newsgroup: it.comp.lang.c++
Titolo: Chiarimenti in VC++

Salve a tutti,
per prima cosa volevo ringraziare chi mi aveva consigliato l'acquisto
del testo di Prosise, infatti molte cose che prima non mi erano
assolutamente chiare adesso invece lo sono.
C'e' pero' ancora una cosa, che per me e' basilare, che ancora non ho
capito... Ormai mi e' chiaro che VC++ e' gestito tutto ad eventi, e
quindi tutto il flusso del prg e' condizionato dai messaggi in arrivo,
solo che io vorrei realizzare un programmino che una volta creata una
finestra, in maniera autonoma, e per questo intendo senza bisogno che
sia necessaria la selezione di un menu oppure di un clicckaggio del
mouse, inizi l'elaborazione di un algoritmo, ad esempio un ordinamento
di un vettore, e poi visualizzi il risultato senza utilizzare le
OnPaint() o OnDraw(). So di chiedere tanto, ma sarebbe possibile
postare o mandarmi un' E-mail di un programmino di esempio che faccia
esattamente queste operazioni? Grazie davvero.
Roberto Maiorca <<Sig8>>

...


ti occorre
Io mi tovo nelle stesse condizioni di Roberto, non riesco a capire con
far si che all'apertura del programma parta automaticamente una
routine senza bisogno di premere un tasto.
Anche se creo una dialog based il problema resta.
grazie 

...

Alla partenza del tuo programma, il framework MFC chiama la
funzione InitInstance del tuo oggetto applicativo.  In quella funzione,
puoi fare quello che occorre.  Il wizard, tipicamente, te ne genera
una in cui vengono analizzati gli eventuali argomenti passati al
programma, poi, se del caso, viene creata la finestra principale.
In un programma MFC dialog-based, come il wizard lo genera,
la InitInstance non fa altro che creare il dialogo come modale,
e terminare l'esecuzione dell'intero programma quando il dialogo
stesso termina (tipicamente a causa di click dell'utente sui
bottoni di OK o Cancel, o altri eventi equivalenti).  In altri tipi
di applicazione, invece, normalmente la finestra principale
("main frame") viene creata e mostrata, dopo di che la funzione
InitInstance ritorna normalmente, cosicche` l'intero programma
si mette ad aspettare eventi e rispondere ad essi.
Se non interessa rispondere ad eventi, dunque, basta che
(magari dopo avere creato e mostrato la finestra principale),
invece di ritornare il controllo al framework, InitInstance faccia
partire l'esecuzione di checche` di altro si desideri (analogamente,
in una applicazione Dialog-based, si puo` non ritornare il
controllo, ma eseguire checche` si desideri, ad esempio
nella funzione OnInitDialog della classe-dialogo).  Questo,
naturalmente, non e' normalmente accettabile, perche`, se
l'applicazione non risponde agli eventi, appare all'utente, e
al sistema, come "piantata" ("not responding"...!).
Di solito, anche se MUX e Roberto non lo chiariscono,
quel che si vuole e` "botte piena E moglie ubriaca": una
applicazione che risponde normalmente agli eventi (ad
esempio, "ridipinge" le sue finestre se vengono coperte
e poi nuovamente scoperte, ecc ecc), MA, se eventi di
qualche importanza non ce ne sono, svolge intanto un
qualche tipo di elaborazione.  Questo si puo` ottenere, in
sostanza, in tre modi:
    -- idle time processing
    -- verifica periodica degli eventi
    -- multithreading
Verifica periodica, e multithreading, sono due ottime
tecniche, ma sono gia` state trattate in passato su
questo gruppo (usare 
http://www.dejanews.com
, e
specificamente la sua funzione Power Search, per
accedere a vecchi post su questo o qualsiasi altro
newsgroup) -- usare PeekMessage come chiave
di ricerca per la verifica periodica, CreateThread o
begin_thread ecc per il multithreading.  Lo idle time
processing, invece, mi pare non sia mai stato qui
trattato (cercando OnIdle, non si trova nulla....), e
quindi penso che possa essere utile offrire una breve
introduzione all'argomento.
Per usare lo idle time processing, bisogna anzitutto
"sminuzzare" l'elaborazione che si desidera in tante,
ma tante "fettine" sottili, da eseguirsi l'una dopo l'altra,
in modo che ciascuna non prenda "troppo" tempo.
Mentre una "fettina" e` in corso di esecuzione,
l'applicazione non rispondera` agli eventi; e` dunque
cruciale che il tempo di esecuzione di una fettina sia
molto corto -- la pazienza degli utenti e` notoriamente
assai limitata; io direi di non superare i pochi decimi
di secondo.  Se e` fattibile questo "sminuzzamento",
basta poi scrivere un override per la funzione OnIdle
del proprio oggetto-applicazione; questa verra`
chiamata, ripetutamente, se non c'e` nessun evento
da servire -- come argomento, ricevera` un LONG
che conta quante volte e` stata chiamata dall'ultimo
evento servito; essa eseguira` la prossima "fettina"
che interessa, poi, se l'elaborazione in questione non
e` ancora del tutto terminata, ritornera` TRUE, in
modo da venir chiamata ancora -- quando, infine,
avra` terminato del tutto, ritornera` FALSE, e allora
non verra` piu` chiamata sino a che l'applicazione non
avra` ricevuto e servito un altro evento.
Come spesso succede per gli override in MFC, cosi`
anche per l'override di OnIdle bisognera` chiamare la
versione della classe-base (CWinApp:OnIdle) per
permetterle di fare il _suo_ idle-processing (gestione
dell'interfaccia-utente e manutenzione di strutture
dati interne).
Facciamo dunque un semplice esempio. Diciamo che
il nostro oggetto-applicazione contiene un grosso
vettore (usero` per illustrazione std::vector, supponendo
di avere i necessari #include <vector> e using) di
double -- migliaia di elementi, o anche un milione,
a seconda di come e` stato fatto partire -- che deve
inizializzare chiamando per ognuno una funzione di
calcolo
    extern double comput(int indice);
che, sulla CPU piu` lenta su cui e` realistico pensare
che giri il nostro programma, puo` richiedere circa
un millisecondo di tempo di esecuzione.  Vorremmo,
se potessimo, scrivere semplicemente nella nostra
CMyApp::InitInstance, dopo avere visualizzato la ns
finestra principale e prima di ritornare il controllo:
    for(int i=0; i<m_nQuanti; ++i)
        m_vet[i] = comput(i);
(supponiamo qui ovviamente che m_vet sia il ns
vettore, e m_nQuanti il numero dei suoi elementi);
ma questo "bloccherebbe" l'applicazione per un tempo
anche lungo -- secondi, ma sino a 15+ minuti! -- in
cui l'utente non avrebbe nessun riscontro dell'esecuzione,
e sicuramente considererebbe "bloccata" l'applicazione.
Proviamo dunque l'approccio "idle time".  Una prima
"soluzione" sarebbe di calcolare un solo valore per
ogni "fettina"; aggiungiamo al ns oggetto di classe
CMyApp un membro m_nFatti, inizializzato a zero,
che dice quanti elementi di m_vet abbiamo gia`
calcolato, e il seguente override di OnIdle:
BOOL CMyApp::OnIdle(LONG nProg)
{
    static BOOL mustDelegate=TRUE;
    if(mustDelegate || nProg==0) {
        // dobbiamo delegare alla classe-madre
        mustDelegate = CWinApp::OnIdle(nProg);
        return TRUE;          // non ancora finito
    } else {
        // mamma ha finito, ecco il ns processing
        if(m_nFatti >= m_nQuanti) {
            return FALSE;    // tutto finito
        } else {
            m_vet[m_nFatti] = comput(m_nFatti);
            ++m_nFatti;
            // eventuale feedback all'utente sul ns
            // progresso sino a questo punto
            return TRUE;      // non ancora finito
        }
    }
}
Questo funziona, ma e` molto inefficiente; c'e`
molto overhead attorno ad ogni chiamata alla
comput(), soprattutto se (come e` sicuramente
il caso!) vogliamo dare feedback all'utente che
gli indichi dove siamo arrivati a calcolare (almeno
una progress bar, magari un qualche display
grafico dei dati gia` calcolati).
Questo perche`, qui, la "granularita`" di una sola
chiamata alla comput() per ogni passata di OnIdle
e` eccessivamente fine; garantisce un ottimo
tempo di risposta agli eventi, non oltre 1 msec,
ma questo e` esageratamente rapido -- certo
potremmo fare _parecchie_ chiamate a comput(),
consumando un certo numero di millisecondi, senza
che questo dia fastidio all'utente; e cosi` facendo
"ammortizzeremmo" l'overhead, migliorando il
"throughput" della nostra elaborazione.  C'e` sempre
un trade-off fra tempo di risposta, e throughput, ma
e` importante poterlo controllare.
Aggiungiamo dunque al nostro oggetto applicativo
un altro campo, m_nBlocco, che dice quante comput()
per volta dobbiamo chiamare.  Possiamo inizialmente
settare m_nBlocco a un valore di minima, prudenziale
(ad esempio, 20 -- garantendo tempo di risposta agli
eventi attorno ai 20 millisecondi), o anche calibrarlo
sulla base della velocita` del processore che l'utente
sta usando, permettere all'utente di modificarlo da una
option page per cambiare i tradeoff throughput/tempo
di risposta, salvarlo nella registry e recuperarlo da essa,
e chi piu` ne ha, piu` ne metta.  Il punto e` che, al posto
delle semplici istruzioni, sopra usate:
            m_vet[m_nFatti] = comput(m_nFatti);
            ++m_nFatti;
avremo invece un loop del genere:
            int imax = m_nFatti+m_nBlocco;
            if(imax > m_nQuanti)
                imax = m_nQuanti;
            for(int i=m_nFatti; i<imax; ++i)
                m_vet[i] = comput(i);
            m_nFatti = imax;
"spalmando" ("ammortizzando") l'overhead e quindi
migliorando in proporzione il throughput.
Naturalmente, non tutti i problemi sono cosi` semplici
da "ritagliare in fettine".  Quando si puo` farlo senza
eccessiva fatica, e` un buon approccio; quando la
fatica diventa eccessiva, il multi-threading appare come
soluzione migliore.  Tendenzialmente, i problemi che
si adattano bene all'approccio idle-time-processing,
e quelli che si adattano bene alla verifica periodica
degli eventi, sono piu` o meno gli stessi, perche`
l'approccio di "verifica periodica" si riduce a "fai un
po' di lavoro, poi controlla se ci sono eventi da gestire,
se si`, interrompi il lavoro [per poi ricominciarlo piu`
tardi da dove era rimasto] e gestisci gli eventi, se no,
continua [sinche` il lavoro non e` tutto fatto]" -- le
"fettine di lavoro" sono forse meno esplicite, ma sono
nondimeno presenti.  Alla fin fine, la scelta fra i due
approcci dipende (a) dalle prestazioni che otteniamo
nei due casi, e (b) da quanto "invasivo" sia ciascuno
dei due in termini di "stravolgimento" necessario al
nostro codice di elaborazione piu` "naturale".
Anche il multithreading puo` essere visto come un
simile "affettamento", con la differenza che i "tagli"
delle fette li decide il sistema, interrompendo il nostro
"thread di lavoro" (a favore del "thread di GUI") in
istanti "arbitrari" rispetto al lavoro compiuto -- il ns
compito diviene allora quello di garantire che questi
tagli arbitrari non abbiano "strani" effetti, se le stesse
strutture devono essere accedute sia dal thread di
GUI sia da quello di lavoro, gestendo coordinazione
e comunicazione fra i due "lati" in questione.  E` in
effetti un approccio completamente diverso in termini
di difficolta`, overhead, e benefici (e al suo interno
si possono distinguere diverse "architetture" a seconda
dell'esatto compito che ci si propone: una cosa e` una
semplice sostituzione dell'"idle time processing", altra
e` trarre pieno beneficio da molteplici CPU nello stesso
PC, o facilitare l'esecuzione "simultanea" di diversi e
molteplici compiti non correlati fra loro...).
Alex