Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
C# / VB.NET - problemi con la gestione dei thread
Forum - C# / VB.NET - problemi con la gestione dei thread

Avatar
Dice (Normal User)
Expert


Messaggi: 238
Iscritto: 26/11/2011

Segnala al moderatore
Postato alle 11:20
Mercoledì, 19/11/2014
Adesso vi spiego cosa faccio:
io ho un progetto windows form, in cui nel costruttore, subito dopo la funzione InizialiseComponent(), vado a far partire un thread (che mi fa da server) in quanto deve essere sempre operativo. Poi con un pulsante faccio partire un altro thread (che fa altre cose). Fin qui no problem; il problema arriva quando voglio chiudere il programma: ho il pulsante ESCI con la quale voglio far chiudere il programma; il fatto è che se faccio semplicemente Close() mi si chiude la form, ma il programma rimane in esecuzione, quindi devo terminarlo "forzatamente" col pulsante di stop.
Allora ho provato a mettere prima la terminazione dei due thread: threadServer.Abort();   progT.Abort();  Close();
Però così facendo, subito dopo threadServer.Abort() la mia form si "blocca", nel senso che non è più accessibile all'utente, e poi non fa più niente, costringendomi di nuovo alla terminazione forzata :(
Cos è che sto sbagliando?

PM Quote
Avatar
Roby94 (Member)
Guru


Messaggi: 1170
Iscritto: 28/12/2009

Segnala al moderatore
Postato alle 22:15
Mercoledì, 19/11/2014
Il sistema è molto semplice ma ti voglio far ragionare un attimo. Allora escludiamo le applicazioni form e prendiamo in considerazione quelle a console; in queste applicazioni abbiamo una funzione principale o punto d'ingresso, Main. Quando vai a lanciare il tuo eseguibile in modo celato viene lanciata la funzione(metodo) Main

Codice sorgente - presumibilmente C# / VB.NET

  1. public static void Main(string[] args)
  2. {
  3.     Console.Write("Console");
  4. }


Se vado a scrivere una semplice console write una volta eseguita, l'applicazione terminerà il suo ciclo di vita naturalmente poiché è arrivata alla fine della funzione Main per quello si tende a mettere un Console.Read(); alla fine del metodo, obbligando il programma ad attendere un intervento dell'utente(sta temporeggiando in attesa che l'utente intervenga).
Bene le stesse considerazioni valgono per i thread creati dall'applicazione padre, quindi se tu scrivi
Codice sorgente - presumibilmente C# / VB.NET

  1. public void TH()
  2. {
  3.     while(true);
  4. }


ti risulta facile capire che a meno che non venga forzata questa funzione non terminerà mai il suo ciclo di vita, non rimane che includere dentro la stessa una condizione di uscita

Codice sorgente - presumibilmente C++

  1. public static bool  IsEnd = false;
  2. public void TH()
  3. {
  4.     while(!IsEnd);
  5. }


Come vedi cosi potrò andare ad intervenire sulla variabile IsEnd per portare a fine il loop presente dentro il thread portandolo quindi alla fine del ciclo naturale di vita, in questo caso non vai a forzare la chiusura.

Per tirare le somme non devi vedere un istanza della classe thread come un altro win form che impedisce la propria chiusura per attendere che si verifichino degli eventi. Ma come una singola funzione che cerca di terminare, quindi devi dargliene la possibilità.

PM Quote
Avatar
Dice (Normal User)
Expert


Messaggi: 238
Iscritto: 26/11/2011

Segnala al moderatore
Postato alle 9:23
Giovedì, 20/11/2014
Il fatto è che il mio thread server non deve mai terminare, a meno che non venga premuto il pulsante per chiudere tutto il programma. Un dettaglio in più: il mio thread "è rappresentato" da una funzione di un altra classe.
Quindi ripetendo: se uso la Abort() nella windows form per bloccare il mio thread, la mia GUI si blocca.
Tu dicevi di porre delle condizioni di terminazione, ma come gia detto, in teoria non ci dovrebbero essere nel mio caso, perchè io voglio che il mio server non termini mai.
In che modo posso risolvere?

PM Quote
Avatar
Dice (Normal User)
Expert


Messaggi: 238
Iscritto: 26/11/2011

Segnala al moderatore
Postato alle 10:44
Giovedì, 20/11/2014
OK Roby94, sono riuscito a risolvere :D
Alla fine ho impostato la proprietà isBackground a true per il thread server; in questo modo quando eseguo la syscall Close() per terminare l'intero applicativo, il thread termina in automatico, senza dover usare il metodo Abort().
OK, il problema l'ho risolto ma non ho la coscienza del tutto apposto :(
M spiego meglio: ho risolto il problema in un modo, ma vorrei delle delucidazioni sul perchè quando usavo la funzione threadserver.Abort() mi si bloccava la gui; ho usato anche il debugger ma niente, dopo abort() rimaneva tutto bloccato :(
Ma scusate: io so che con Abort() termino un thread; io nella windowsForm vado a fare: threadServer.Abort(). Il controllo non dovrebbe poi passare ancora alla windowsForm? O forse il controllo era ritornato alla windowsForm ma mi si blocca comunque? Ma perchè è bloccato?
Scusate se forse sono un po' "rompi scatole" con tutte queste domande, è che voglio capire veramente cosa c'era di sbagliato :(

Grazie mille in anticipo per il supporto :)

PM Quote
Avatar
Roby94 (Member)
Guru


Messaggi: 1170
Iscritto: 28/12/2009

Segnala al moderatore
Postato alle 18:23
Giovedì, 20/11/2014
Perche non hai fatto quello che ti avevo chiesto di fare, ragionare.
Ti riporto la dicitura che descrive il metodo Abort.

Viene generata un'eccezione ThreadAbortException nel thread in cui viene richiamato, per iniziare il processo di terminazione del thread. La chiamata a questo metodo determina in genere la fine del thread.

Abort non termina il thread, forse non è cosi chiaro; ma come scritto sopra Abort genere un eccezione nel thread che andrà gestita.
http://msdn.microsoft.com/it-it/library/cyayh29d%28v=vs.11 ...

Quello che ho cercato (aggiungerei inutilmente) di farti capire nel post precedente è come si dovrebbe comportare il thread. il thread va a chiudersi automaticamente quando viene completato, l'eccezione scaturita da Abort se gestita dovrebbe fare in modo che il metodo giunga alla fine della sua vita.

Ora voglio riprovare a farti capire quello che ti ho spiegato in precedenza, presta attenzione.
Mettiamo che tu abbia una classe
Codice sorgente - presumibilmente C++

  1. public class A {
  2.   static public bool x = true;
  3.  
  4.   static public  void TH()
  5.   {
  6.     while(A.x) {
  7.       Sleep(1000);
  8.     }
  9.   }
  10. }


Come vedi se avvio un nuovo thread con metodo TH() questo continuerà a loop il while e di conseguenza lo Sleep all'interno che simula un operazione dispendiosa in termini di calcolo. Ora se voglio andare a terminare il thread andrò ad agire sulla variabile statica x che non si trova direttamente dentro il thread generato anche se questo puo accedervi, in questo modo appena porterò x a false il thread finirà il ciclo corrente di while e poi ne uscirà. x essendo publico puo essere modificato da qualsiasi punto dell'applicazione padre, quindi capisci che la tua procurazione non è fondata, io posso terminare il thread da ogni posizione nel codice anche dall'interno di un ipotetica classe B.
Spero di averti fatto capire il procedimento.

Ora visto che hai tirato in causa Abort voglio che tu capisca perché è da preferirsi la soluzione di prima a questo metodo.
prendiamo di nuovo la classe A e modifichiamo TH perché gestisca Abort.
Codice sorgente - presumibilmente C++

  1. static public  void TH()
  2. {
  3.   try {
  4.     while(true) {
  5.       Sleep(1000);
  6.       Save();
  7.     }
  8.   } catch(ThreadAbortException ex) {
  9.     Console.Write("Il thread termina");
  10.   }
  11. }


Ok allora vediamo un po le modifiche apportate, abbiamo inserito un blocco try con un catch sull'eccezione ThreadAbortException quando chiameremo Abort l'eccezione verrà lanciata e indipendentemente dallo stato di esecuzione di TH si andrà ad eseguire il write, fatto questo il thread si concluderà, sembra tutto ok ma vediamo un caso particolare, prendiamo che Sleep sia la simulazione di un calcolo oneroso e necessario per il sostentamento dell'applicazione e che ogni volta che questo viene concluso Save salvi il risultato per il thread principale, mettiamo ora che il flusso di esecuzione vada a trovarsi in mezzo a sleep e save mentre viene a verificarsi l'eccezione, cosa succederebbe? Ovviamente il calcolo è finito e il risultato andrebbe salvato ma questo non accade perche il flusso di esecuzione va a spostarsi sul write facendo in modo che save non venga eseguito. Nella maggior parti dei casi quando si usa il metodo abort bisognerebbe aggiungere al blocco try un blocco finaly che si occupi di gestire lo stato in cui si trova il metodo quando viene a generarsi l'eccezione, proprio per andare a colmare i problemi che causa questa interruzione improvvisa. Credo tu possa capire ora perche è consigliabile il primo metodo che permette alla funzione di concludersi in modo naturale senza lanciare eccezioni, che se non vengono gestite andranno a creare problemi ad un livello piu basso nel tuo caso alla GUI.

Spero di essere stato il più esplicativo possibile, cerca di riflettere sempre su tutte le implicazioni che comporta una determina tecnica e perché te ne venga suggerita un altra, è giusto non fidarsi alla cieca ma dopo lo scetticismo va applicata un acuta riflessione e non un salto della fede verso un metodo a caso :P

PM Quote
Avatar
Dice (Normal User)
Expert


Messaggi: 238
Iscritto: 26/11/2011

Segnala al moderatore
Postato alle 20:04
Giovedì, 20/11/2014
UNA SOLA COSA: PERFETTO!!!! :)
UN SUPER MEGA GRAZIE PER I CHIARIMENTI ;)

Adesso ti dico una cosa che non avevo capito: il fatto che Abort() lanciava un'eccezione che poi sarebbe dovuto andare gestita l'avevo letto e capito, però:
1)siccome quando eseguivo e poi terminavo il programma non mi compariva nessun errore (exception) non pensavo fosse quello il problema XD
2)avevo capito male la gestione dell'eccezione: io il try/catch lo facevo nell'evento del pulsante, quindi nella classe della windowsForm; quando debuggavo mi si bloccava sempre all'Abort() e non mi entrava nel blocco catch{}; questo è un altro motivo del perché pensavo che l'exception non centrasse nulla XD
3)come ho letto nel materiale ufficiale MSDN, e come me lo hai ricordato anche tu, la descrizione del metodo Abort() dice: "...La chiamata a questo metodo determina IN GENERE la fine del thread"; mi veniva da pensare che qualche volta lo fa terminare e qualche volto no XD

Detto tutte le mie vecchie idee (scusa se possono sembrarti stupide :(  è che avevo capito male veramente :(  ;)  vediamo se ho capito bene come funziona: io adesso che ho usato la proprietà isBackground = true, mi verrei a trovare nella situazione di cui mi parlavi: l'esecuzione può venir interrotta in qualsiasi istante, senza garantire il completamento del lavoro.
Dico bene?
Dimmi se ho detto un'altra delle mie baggianate :D

Ripeto di nuovo: un super mega grazie per la spiegazione :) te ne sono grato veramente ;)
Adesso metto in pratica gli insegnamenti ;)  e ti farò sapere se ho qualche altro problema

PM Quote
Avatar
Roby94 (Member)
Guru


Messaggi: 1170
Iscritto: 28/12/2009

Segnala al moderatore
Postato alle 0:17
Venerdì, 21/11/2014
Non conoscevo la proprietà IsBackground, ora ti posso dire che la risposta è si, in effetti rischi che il processo venga interrotto senza essere completato, e sinceramente non credo ci siano grandi possibilità di gestione, io ti consiglierei di intercettare l'evento closing del form principale e condurre a conclusione il thread in modo naturale, o anche con Abort se gestito, in modo da avere almeno l'esecuzione di un blocco finaly che si occupi di una conclusione "non dannosa". Ovviamente tutte queste considerazioni vanno fatte valutando caso per caso. Se la morte del processo thread in un momento casuale non porta a problemi la proprietà IsBackgound potrebbe anche essere messa a true. Rimane sempre pratica buona e giusta fermare server, eseguire la chiusura di eventuali connessioni ecc quando il programma deve terminarsi, ripeto, cosa buona e giusta, non obbligatoria.

Per la eccezione (1) credo sia un discorso legato al debugger, probabilmente questo va a segnalarti eccezioni solo se non vengono gestite al primo livello (GUI), possiamo solo fare ipotesi finché non andiamo a leggerci la guida completa del debugger, cosa che preferirei evitare :) anche perché conoscendo il dettaglio con cui vengono scritti i datasheet dubito potremo trovare risposta, alla fine se metti in piedi un codice corretto poco ti importa che il debugger non ti segnali un errore ma si impalli, l'importante è che tutto funzioni correttamente.

(2) Affidiamoci al fatto che col try in posizione corretta non si verifichino piu problemi ;)

(3) il "in genere" perché sta all'utente decidere cosa fare, potrebbe anche gestire l'eccezione e ignorarla.

In ogni caso non hai fatto considerazioni stupide, l'argomento Thread è certamente uno dei piu ostici ad un primo confronto (anche ad un secondo...) soprattutto se si interagisce con un controllo(metodo Invoke), l'importante è arrivare a saperli usare correttamente.

Ultimissima considerazione che voglio fare(questo non chiude il topic se hai altri dubbi possiamo cercare di chiarirli), il nome del metodo Abort credo sia stato assegnato per dare l'idea di un processo che viene troncato di netto senza grandi considerazioni, pensa alla terminologia "missione abortita", la missione non termina in modo corretto ma viene fermata causa dei problemi, questa differenza di nome da un metodo Close può far riflettere sulle implicazioni di Abort.

Ultima modifica effettuata da Roby94 il 21/11/2014 alle 0:20
PM Quote