Visualizzazione dei risultati da 1 a 8 su 8
  1. #1
    Utente di HTML.it
    Registrato dal
    Aug 2012
    Messaggi
    12

    [C#] Operazione cross-thread non valida

    Premessa.
    Sono un firmwarista assuefatto dal linguaggio assembler (Atmel, microchip, ecc) che ogni tanto fa firmware anche in ANSI C.
    E’ da un mese che ho acquistato un paio di chili di libri e sto cercando di imparare C# su visual studio 2010 per creare applicazioni windows form capaci di interfaccarsi alle schede che progetto. Schede che verso il PC comunicano con seriali (COM) o come ultimamente succede, con prese di rete.

    Sono arrivato a un punto che non mi piace. Qualcosa che mi fa NON piacere questo genere di linguaggio e vorrei tanto che qualcuno mi dicesse: “Stai sbagliando, la cosa è più semplice e si fa così:”

    Ora espongo il mio problema partendo dall’inizio.

    Tra le innumerevoli applicazione che ho realizzato per “allenarmi”, quasi tutte hanno una seriale come porta di interfacciamento.
    Nelle schede che progetto, come sistema di comunicazione uso un protocollo abbastanza semplice e sicuro composto da vari byte di controllo cecksum e altro. Una delle prime cose che ho fatto è stato quello di implementare la codifica e decodifica di tale protocollo a lato PC.
    Un metodo prende un array di byte, lo trasforma secondo il protocollo e poi lo invia.
    L’alto metodo riceve i byte codificati dalla seriale, li decodifica e li passa a un array formattati.
    Fino a qui tutto OK.

    Una delle cose interessanti di questo linguaggio (e penso anche di altri) è quello di poter costruirsi la propria raccolta di classi e di compilarle come delle .dll da usare in altri programmi.
    Nel costruire la mia .dll che da sola facesse la codifica e decodifica dei miei dati dovevo creare una classe che fondamentalmente avesse i 2 metodi sopra citati e ovviamente istanziare una porta.
    Per gestire la ricezione, utilizzo l’evento SerialDataReceivedEventArgs che ha la classe Serialport.
    All’interno della mia:
    codice:
    private void EventoRicezionePortaSeriale(object sender, SerialDataReceivedEventArgs e)
    {
    //ci sono una serie di controlli dei byte ma ciò che è più importante è che quando questo evento,
    //trova una trasmissione valida, deve generare un altro evento per far si che la classe principale //venga a sapere che è avvenuta una ricezione valida.
    }
    Per centrare l’obbiettivo mi sono fatto 3 giorni intensi sui delegati e gli eventi e apparentemente ne sono venuto a capo creando un delegate, un evento e una classe:
    codice:
    public delegate void MessaggioTHARGAricevutoEventHandler(object sender, MessaggioTHARGAricevutoEventArgs e);
    public event MessaggioTHARGAricevutoEventHandler MessaggioTHARGAricevuto;
    public class MessaggioTHARGAricevutoEventArgs : EventArgs
        {
        }
    Devo ammettere che non ho ben capito del tutto perché funziona ma quello che volevo che facesse lo fa, ma con un problema che ora andrò ad esporre.

    Nel provare se la mia .dll funziona, ho fatto un programmino stupido
    Che istanzia la classe principale della mia .dll ne prende il metodo di ricezione e lo collega a una textbox per mostrare il risultato. Nel PC è connessa una mia scheda che alla pressione di un pulsante, manda un messaggio codificato attraverso la seriale.
    Il programmino è semplice e lo posso postare tutto:

    codice:
    public partial class Form1 : Form
        {
            public TARGHAprotocoll Protocollo = new TARGHAprotocoll();
            public Form1()
            {
                InitializeComponent();
            }
     
    private void Form1_Load(object sender, EventArgs e)
            {
                Protocollo.PortaComunicazione.PortName = "COM1";
                Protocollo.PortaComunicazione.BaudRate = 115200;
                Protocollo.PortaComunicazione.Open();
                Protocollo.MessaggioTHARGAricevuto += new TARGHAprotocoll.MessaggioTHARGAricevutoEventHandler(Protocollo_MessaggioTHARGAricevuto);
                textBox1.Text = "Attesa ricezione";
            }
            private void Protocollo_MessaggioTHARGAricevuto(object sender, MessaggioTHARGAricevutoEventArgs e)
            {
                textBox1.Text += "Ricezione arrivata";
            }
    Mettendo il break point in
    textBox1.Text += "Ricezione arrivata";
    All’invio del messaggio da parte della mia scheda il programma si ferma proprio li. Segno che la dll ha ricevuto il messaggio, lo ha ritenuto giustamente valido e mi ha fatta scatenare l’evento proprio come mi ero prefissato. Con il debugger ho controllato che effettivamente la dll ha pronto il messaggio formattato.
    Il problema è da qui in poi. Se tento di far eseguire la semplicissima istruzione successiva
    textBox1.Text += "Ricezione arrivata";
    mi esce un’errore che dice che:

    InvalidOperationException non è stata gestita
    Operazione cross-thread non valida: è stato eseguito l’accesso al controllo ‘textbox1’ da un thread diverso da quello da cui è stata eseguita la creazione.

    Ma che vuol dire?
    Sono a un passo dal traguardo e mi capita anche questa?

    Cercando e ricercando soluzioni a questo problema ho dovuto fare così:

    codice:
    private Thread demoThread = null;
    delegate void SetTextCallback(string text);
    private BackgroundWorker backgroundWorker1;
     
    private void Protocollo_MessaggioTHARGAricevuto(object sender, MessaggioTHARGAricevutoEventArgs e)
            {
                this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
                this.demoThread.Start();
            }
            private void ThreadProcSafe()
            {
                this.SetText("Ricezione arrivata");
            }
            private void SetText(string text)
            {
                // InvokeRequired required compares the thread ID of the
                // calling thread to the thread ID of the creating thread.
                // If these threads are different, it returns true.
                if (this.textBox1.InvokeRequired)
                {
                    SetTextCallback d = new SetTextCallback(SetText);
                    this.Invoke(d, new object[] { text });
                }
                else
                {
                    this.textBox1.Text = text;
                }
            }
    Sinceramente non so cosa ho fatto. Ho adattato un esempio trovato su msdn. La cosa però NON MI PIACE.
    Possibile che debba riempire con chilometri di codice un’applicazione così stupida?
    Spero vivamente di essere io che sto complicando il tutto e chiedo un’aiuto da parte vostra.
    Il mio sospetto è che non vada bene creare un evento all’interno di un altro evento.
    C’è un modo semplice per far funzionare la cosa.

    Ho aggiunto un semplice tasto con questo evento:
    codice:
    private void button1_Click(object sender, EventArgs e)
            {
                textBox1.Text += "Tasto premuto";
            }
    E funziona.
    Perché questo (che tra l’altro è subito sotto) non va?
    codice:
    private void Protocollo_MessaggioTHARGAricevuto(object sender, MessaggioTHARGAricevutoEventArgs e)
            {
                textBox1.Text += "Ricezione arrivata";
            }
    Grazie in anticipo.

  2. #2
    Evidentemente la tua libreria di comunicazione lavora da un thread separato; nel momento in cui sollevi l'evento, chiami il delegato da un thread indipendente da quella della GUI. Per questo motivo, nel delegato vai a modificare un oggetto GUI da un thread non-GUI, il che non è consentito (in generale, gli oggetti GUI non sono thread safe).
    Per questo motivo è necessario fare il giretto che hai visto: la "Invoke" di fatto non fa che invocare il delegato specificato dal thread della GUI dell'oggetto su cui viene chiamata, thread da cui è possibile modificare gli oggetti GUI.
    Questo "sotto il cofano" è implementato con una SendMessage sulla finestra specificata: il messaggio viene postato sulla coda dei messaggi della finestra, e viene recuperato nel thread GUI (dove gira la message pump); al momento di processarlo, viene chiamato il delegate specificato dal thread giusto, restituendo il risultato poi al thread della Invoke.
    Amaro C++, il gusto pieno dell'undefined behavior.

  3. #3
    Utente di HTML.it
    Registrato dal
    Aug 2012
    Messaggi
    12
    Grazie MItaly
    Grazie della delucidazione.
    Devo ammettere che non ho capito bene quello che mi hai scritto. Sicuramente perchè:
    -mi manca ancora il concetto di thread
    -di conseguenza non ho capito come facci a far lavorare la mia applicazione su thread diversi
    -ho investito 3 giorni su delegati ed eventi su 3 libri diversi e ancora non li ho assorbiti del tutto.
    Assomigliano agli interupt dei microcontrollori ma in assembler e ANSI C è più semplice gestirli .
    Ora rileggerò parola per parola quello che mi hai scritto e cercherò di capire il concetto.
    Grazie ancora

  4. #4
    Quote Originariamente inviata da Arago Visualizza il messaggio
    Grazie MItaly
    Grazie della delucidazione.
    Devo ammettere che non ho capito bene quello che mi hai scritto. Sicuramente perchè:
    -mi manca ancora il concetto di thread
    Il concetto è abbastanza semplice: nello stesso processo, ci sono più "fili" di esecuzione che procedono in parallelo; puoi pensarli come più processori che lavorano indipendentemente ma avendo condivisa la memoria (in pratica se sono in esecuzione più thread dei core disponibili la cosa viene gestita "dando" la CPU per un tot di tempo a turno a ciascun thread).
    -di conseguenza non ho capito come facci a far lavorare la mia applicazione su thread diversi
    Che cosa stai usando per gestire i byte ricevuti dalla seriale? Controlli con un timer ogni tot? Ricevi degli eventi da un oggetto usato per gestire la porta seriale?
    -ho investito 3 giorni su delegati ed eventi su 3 libri diversi e ancora non li ho assorbiti del tutto.
    Fai conto che un delegate è concettualmente simile ad un puntatore a funzione.
    Ultima modifica di MItaly; 28-03-2014 a 01:18
    Amaro C++, il gusto pieno dell'undefined behavior.

  5. #5
    Utente di HTML.it
    Registrato dal
    Aug 2012
    Messaggi
    12
    Quote Originariamente inviata da MItaly Visualizza il messaggio
    Che cosa stai usando per gestire i byte ricevuti dalla seriale? Controlli con un timer ogni tot? Ricevi degli eventi da un oggetto usato per gestire la porta seriale?
    Uso una SerialPort.
    Nella dll (per ora) c'è solo un'istanza a una serial port:
    codice:
    //Costruttore
            public TARGHAprotocoll()
            {
                //Costruttore
                PortaComunicazione = new SerialPort();
                PortaComunicazione.DataReceived += new SerialDataReceivedEventHandler(EventoRicezionePortaSeriale);
            }
            public SerialPort PortaComunicazione;
    Tutto il codice che decodifica il pacchetto è all'interno dell'evento SerialDataReceivedEventArgs.
    Non vengono richiamati metodi al suo interno. Ci sono dei for, Case switch e degli if che controllano tutti i byte in arrivo. Il codice è lungo da postare ma provo a riassumerlo
    codice:
    private void EventoRicezionePortaSeriale(object sender, SerialDataReceivedEventArgs e)
            {
                int numeroDiDatiRicevuti = PortaComunicazione.BytesToRead;    
                byte[] bufferRxPresenti = new byte[numeroDiDatiRicevuti];
                int numeroDatiDaLeggere = PortaComunicazione.Read(bufferRxPresenti, 0, numeroDiDatiRicevuti);                                                              
                if (numeroDatiDaLeggere == 0)                                                          
                    return;                                                                            
                for (int tempIndex = 0; tempIndex < numeroDiDatiRicevuti; tempIndex++)
    
    //Da qui partono tutta una serie di controlli che decodificano il messaggio
    
    
    //Ad un certo punto, quando viene decodificato il messaggio, genero un nuovo evento. 
    //Proprio l'evento che poi mi da l'errore descritto in questo post
    MessaggioTHARGAricevuto(this, new MessaggioTHARGAricevutoEventArgs());
    //E come vedi, l'evento viene generato prima che si chiuda la graffa 
    //che termina il SerialDataReceivedEventArgs
            }
    Come altra informazione posso dire che l'idea del dll mi è venuta perchè questo protocollo lo dovrò usare costantemente in diversi progetti. Invece di scriverlo o copiarlo ogni volta ho pensato di fare una dll fissa che contenesse tutto il necessario.
    Prima di questo facevo proprio il copia incolla di tutto il codice e facendo così funzionava.
    Intendo, avere l'istanza della seriale e tutti i metodi nel medesimo file (che però era un form).
    Effettivamente quando il codice doveva dichiarare un messaggio valido usavo un comando diverso:

    this.Invoke(new EventHandler(ScritturaSuDisplayOrigine));
    Ultima modifica di Arago; 28-03-2014 a 09:58

  6. #6
    Tutto torna: se leggi la documentazione dell'evento SerialPort.DataReceived, troverai:
    L'evento DataReceived viene generato su un thread secondario quando dall'oggetto SerialPort vengono ricevuti i dati. Poiché questo evento viene generato in un thread secondario e non in quello principale, il tentativo di modificare alcuni elementi del thread principale, come gli elementi di interfaccia, potrebbe generare un'eccezione thread. Se è necessario modificare alcuni elementi nelle classi principali Form o Control, eseguire il postback delle richieste di modifica utilizzando il metodo Invoke, che eseguirà il lavoro nel thread appropriato.
    (probabilmente questo dipende dal fatto che l'oggetto SerialPort dietro le quinte crea un thread secondario, in cui fa chiamate bloccanti per leggere i dati da seriale, e ogni volta che la chiamata di lettura ritorna solleva l'evento DataReceived)
    Amaro C++, il gusto pieno dell'undefined behavior.

  7. #7
    Utente di HTML.it
    Registrato dal
    Aug 2012
    Messaggi
    12
    Ora è chiaro perchè questo succede.
    Grazie di avermi seguito.
    Ho però un'ultima richiesta.
    Ho passato del tempo davanti questo giretti che hai chiamato "sotto il cofano"
    l'ho ridotto e adattato alla mia applicazione.
    Ti dico già che funziona ma vorrei che mi spiegassi meglio perchè
    Ora posto il codice e vorrei ti soffermassi sui commenti e mi dicessi se sei d'accordo su quello che ho scritto o se c'è qualcosa da rivedere.
    codice:
    private Thread ThreadDiAppoggio;
            delegate void SetTextCallback();
            private void Protocollo_MessaggioTHARGAricevuto(object sender, MessaggioTHARGAricevutoEventArgs e)
            {
                ThreadDiAppoggio = new Thread(new ThreadStart(MetodoRichiamoThreadTHARGA));//   Arrivo qui e so che l'evento è stato generato da un tread non gui
                ThreadDiAppoggio.Start();                                                  //   Quindi ne istanzio uno nuovo e gli "dico di avviarsi" partendo da MetodoRichiamoThreadTHARGA
            }                                           //                                                          |
            private void MetodoRichiamoThreadTHARGA()   //Qui parte                                          <------|
            {                                           //e vado a richiamare il metodo "sotto il cofano"
                MetodoDaTreadGiustoTHARGA();            //
            }                                           //
            private void MetodoDaTreadGiustoTHARGA()    //
            {                                           //
                if (button1.InvokeRequired)             //al primo if chiedo se è possibile modificare la gui       |--->Ora richiedo all'if lo stato del bottone e questa volta
                {                                       //in questo caso interrogando un tasto presente in gui      |   mi dice che è modificabile probabilmente perchè è quì che
                    SetTextCallback delegato;           //NON si può! Allora "Faccio puntare al delegato            |   ritorno al tread giusto.
                    delegato = MetodoDaTreadGiustoTHARGA;//questo stesso metodo                                     |           |
                    Invoke(delegato);                   //e lo invoko------------------------------------------------           |
                }                                       //                                                                      |
                else                                    //                                                                      |
                {                                       //                                                                      |
                    QuelloCheVolevoFareQuandoArrivavaIlMessaggioTHARGA();//                                                     |
                }                                                        //                                                     |
            }                                                            //                                                     |
            private void QuelloCheVolevoFareQuandoArrivavaIlMessaggioTHARGA()<--------------------------------------------------|
            {// Il nome di questo metodo non presta a commenti!!
                textBoxTHARGA.Text = null;
                for (int counter = 0; (counter < Protocollo.MessaggioRicevuto.Length); counter++)
                    textBoxTHARGA.AppendText(Protocollo.MessaggioRicevuto[counter].ToString());
            }

  8. #8
    Utente di HTML.it
    Registrato dal
    Aug 2012
    Messaggi
    12
    Purtroppo qui va a capo da solo e non si segue bene le freccette che ho fatto sui commenti. Se copi e incolli su un file di testo vedrai meglio

    Rettifico. Con Chrome non si vede. Con Firefox si.
    Ultima modifica di Arago; 28-03-2014 a 20:11

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.