Errore su critical section

Buongiorno,
dei threads condividono delle funzioni con un timer per l’aggiornamento dei valori sulla window.

Le funzioni condivise sono cos organizzate:

Function DevicecCount() as integer ThreadCritical.enter Dim Res as integer Try Res = Cdevice.ubound + 1 Catch End Try ThreadCritical.leave Return res End Function

Occasionalmente mi si presenta un errore sul comando ThreadCritical.leave

Il messaggio di errore il seguente : The thread which entered the CriticalSection must be the thread to leave the CriticalSection

Che cosa lo genera ?

Grazie per il vostro supporto !

Dipende da come strutturata la cosa.
Dove si trova ThreadCritical, quando lo utilizzi (solo qui), chi chiama questo metodo?

Ho creato una classe per gestire la comunicazione con varie periferiche via rete.
La classe contiene un thread per la gestione (protocollo, timeout, connessione…) , un oggetto critical condiviso in tutta la classe per la gestione delle variabili fra il thread e l’ applicazione tramite le funzioni condivise.

Quindi sia che acceda alle funzioni dal thread dell’ oggetto stesso o dall’ applicazione ho la certezza di non avere “collisioni” per l’ accesso alle risorse.

Nota:
Normalmente implemento questa classe in un’ altra per la gestione multipla delle connessioni e anch’ essa ha un oggetto critical per lo stesso motivo.

Aggiornamento:
Ho provato a sostituire gli oggetti criticalsection con oggetti semaphore e sembra che ora funzioni.
Non ho capito per a cosa serve il parametro di semaphore.
L’ help spiega che sono il numero di risorse che pu utilizzare, ma se il mio thread accede ad una funzione alla volta a cosa mi serve impostare pi risorse ?
Cosa cambia a livello pratico impostarlo a 5 invece che 1 ?

Il semaforo permette a più thread di riservare l’accesso, per cui non hai più il problema dell’esclusività.

La critical section sarebbe più opportuna.
Una cosa che potresti provare è, mettendo un identificativo ad ogni thread, usare il debuglog (o se ci vuole tempo un sistema di logging) per vedere chi entra nel metodo, chi supera l’accesso e così via.
Ad esempio potresti assegnare l’id ad una proprietà dopo enter e verificarla prima del leave e cancellandola al leave. in questo modo puoi vedere chi sta cercando di entrare e se entra se la variabile è libera (in pratica quello che fa internamente la critical section)
Potrebbe essere un caso di malfunzionamento di enter

Se si tratta di aggiornare valori letti ripetutamente (potresti saltare una lettura usando il tryenter)

Ho effettuato le seguenti modifiche per scovare il problema:

[code]Function DeviceCount(id as string) as integer
'La variabile ID passata alla funzione indica quale processo fa la chiamata

ThreadCritical.Enter
Dim Result as integer
Dim Localtest as string
Dim DiffEnterDelay as double
'Verifica se il Critical.enter gi stato utilizzato
if testid <> “” then
system.DebugLog("Critical gi aperto da "+ testid) 'Questa riga di comando non viene mai eseguita
end if

'Chiamate da processi diversi
if testid = “” then testID = id
if testid <> id and testID <> “” then
system.DebugLog("Critical aperto da "+ testid)
system.DebugLog("Critical aperto senza aspettare la chiusura da "+ id)
end if

Try
result = Cdevices.Ubound + 1
Catch
result = -1
end try
ThreadCritical.Leave
'Se tutto si svolge correttamente vengogo resettate le variabili
testid = “”
return Result
end function
[/code]

Il codice cos organizzato:
L’ oggetto principale gestisce l’ array di oggetti che gestiscono le connessioni IP.
Sia la classe principale che la classe dell’ array hanno una propria critical section (su tutte le funzioni)

Risultato dei test:
I critical sono sempre inizializzati dall’ oggetto “timer” quindi ho aggiunto un contatore al iD in modo da capire se la richiesta fosse la stessa o due successive

if AA.RTU_Device_Count(LocalSelectedRtuSlot,"timer"+str(passi)) >0 then passi = passi + 1 'Passi una variabile globale .... end if

  • Anche con l’ ultima modifica il risultato sempre uguale:
    id = timer33
    testid = timer33
    id = timer26
    testid = timer26

  • Se attivo un solo thread di comunicazione il timer non d alcum problema ma se i thread sono 2 o pi l’ errore si ripresenta.

  • La condivisione dei dati fra i threads non ha mai generato problemi

Buongiorno Sacha,
spero di comprendere l’intento del tuo programma anche se non sono certo di capirne esattamente l’implementazione.
Dopo questa premessa mi permetto di fare presente alcune cose che potrebbero essere la causa delle stranezze che riscontri.

Gli eventi generati da timers, sockets e seriali sono sempre eseguiti nel contesto del thread principale dell’applicazione.
Prendo come esempio i timers.
Non importa dove un timer sia stato dichiarato o dove il timer sia stato attivato: l’evento Action() viene sempre eseguito nel contesto del thread principale.
Questo specifico comportamento dipende dal “finto” multithreading implementato nel framework di Xojo.
Utilizzo il termine “finto” in quanto in un’applicazione Xojo nessuna elaborazione avviene in parallelo: i thread sono gestisti secondo il principio di “cooperative scheduling”.
E’ sempre eseguito solo il codice di un solo thread indipendentemente dal sistema operativo e dalle relative possibilita’ di multithreading.

Tornando al programma, se l’intento e’ di gestire gli eventi di timer, socket e seriali nel contesto di un thread questo non e’ possibile.
E’ possibile verificare quanto sopra descritto utilizzando negli eventi il metodo Application.CurrentThread.
Spero di non avere frainteso la struttura del programma e conseguentemente la vera causa delle anomalie riportate.

Saluti.

Grazie Maurizio per le informazioni aggiuntive che mi hai fornito. Avevo gi trovato qualcosa che rendeva attenti sul fatto che il multithreading fosse “virtuale” ma non sapendo cosa fare (la documentazione al riguardo scarsa) ho deciso che ogni classe doveva avere una sua critical section per gestire i suoi elementi e che gli oggetti dovevano essere interrogati periodicamente sul loro stato senza l’ ausilio di un evento.
I timers presenti nelle classi servono solo a verificare se il thread ancora in esecuzione e se il caso lo riavvia, quindi nessun conflitto con altri threads.

Come detto in precedenza ho sostituito i critical section con i semaphore con il numero di risorse impostato a 2 e sembra funzionare tutto senza alcun problema. (Test in corso da 2 giorni su 6 periferiche).

Nota: Se Il numero di risorse impostato a 1 il programma si blocca quasi subito e dopo averlo chiuso tramite l’ interfaccia di windows Xojo rimane lentissimo fintano che non viene riavviato.

Se pu aiutare, in questo link c’ lo schema di principio di come organizzato il progetto. Un’ immagine vale quanto 1000 parole…

Buongiorno Sacha,
il blocco del programma utilizzando un semaforo impostato a 1 e’ l’indicazione del manifestarsi del problema riportato inizialmente.
Nel programma e’ presente qualcosa non correttamente strutturato.
Il problema su CriticalSection segnala che il metodo Leave() non era eseguito dallo stesso thread che aveva in precedenza eseguito il metodo Enter().

Potrebbe essere presente una condizione che, anche inconsapevolmente, provochi tra Enter() e Leave() un cambio di thread, una CriticalSection sia utilizzata per serializzare l’accesso a piu’ entita’ tra loro distinte, il tentativo di acquisire piu’ volte la stessa risorsa da parte dello stesso thread (conseguenza della serializzazione di entita’ distinte)?

N.B.
CriticalSection tiene traccia del thread che ha eseguito Enter() e pretende che sia lo stesso thread ad eseguire Leave().
Inoltre il thread che ha eseguito Enter() senza aver ancora eseguito Leave() puo’ eseguire nuovamente Enter() senza blocco: Leave() deve essere comunque eseguita lo stesso numero di volte di Enter() per il rilascio della CriticalSection.

Un semaforo puo’ invece essere acquisito (Signal) da un thread e rilasciato (Release) da un altro thread.
Il numero di Signal possibili senza l’esecuzione di Release prima della condizione di blocco e’ sempre pari al numero di risorse indicate nel costruttore del semaforo stesso.

Saluti.

Guardando le indicazioni di Maurizio (che ringrazio infinitamente) ho capito che il problema risiede nell’ accesso alla risorsa più volte dallo stesso thread.

Credevo che una volta effettuato l’ enter non fosse più possibile eseguirne un’ altro in quanto l’ utilizzo esclusivo della risorsa era già stata assegnata.

In questo caso è lecito usare semaphore al posto della Critical Section ?

Se ho un semaphore(2) e come in questo caso utilizzo la stessa risorsa, non rischio una collisione visto che ho impostato a 2 le risorse disponibili ?

La doppia “entrata” nella Critical Section è consentita solo se è effettuata dallo stesso thread ?

L’ impostazione del numero delle risorse serve solo a limitare il carico sulla risorsa ?

Ringraziandovi ancora per il vostro aiuto…

Saluti

Sacha

E’ lecito e previsto come riportato dalla documentazione CriticalSection.Enter cosa che non e’ possibile fare con un semaforo.
CriticalSection tiene traccia di chi ha accesso alla risorsa quindi del thread che ha eseguito Enter() mentre il semaforo no.
Per ogni Enter() deve comunque essere eseguita una corrispondente Leave().

Nei commenti precedenti Antonio ha indicato la Critical Section come la più opportuna ma non ha capito per quale motivo.
Riguarda l’ esclusività o altro ?

Il suggerimento di Antonio e’ corretto in base alla domanda posta.
Vediamo di chiarire con un semplice paragone la differenza dei due componenti.

Una CriticalSection e’ paragonabile ad una porta di sicurezza all’ingresso di una banca: si entra uno alla volta e solo quando chi e’ entrato in precedenza e’ uscito.
Il componente tiene traccia della persona all’interno.

Un semaforo e’ paragonabile alla disponibilita’ di automobili di un autonoleggio: il numero complessivo di automobili in dotazione e’ il valore valore indicato nel costruttore del semaforo.
Il componente tiene traccia di quante automobili sono ancora disponibili per il noleggio e non importa se chi noleggia l’auto sia la stessa persona che la riconsegna.

A questo punto il problema e’ cosa implementa il programma.
CriticalSection si utilizza generalmente per serializzare l’esecuzione di gruppi di istruzioni che se fossero eseguite da piu’ thread in parallelo renderebbero incoerenti strutture dati tra loro connesse: un semaforo puo’ svolgere questo compito solo se inizializzato a 1.

Non sappiamo esattamente cosa contenga il programma quindi dare indicazioni esatte su quale componente utilizzare non e’ possibile: cosa deve essere serializzato e tracciato?

Non mi resta a questo punto che chiedere di risolvere il seguente problema utilizzando le descrizioni sopra riportate.
Un noleggio auto dispone di 10 automobili ma e’ presente un solo un impiegato per la gestione della pratica di noleggio.
Tutte e 10 le automobili sono disponibili e si presentano 10 clienti ma l’impiegato puo’ gestire un solo cliente alla volta.
Utilizziamo semafori, CriticalSection o qualsiasi altro componente e a che scopo?

Buon lavoro.

Ho capito.
I parallelismi che hai fatto rendono bene l’ idea.
Grazie Maurizio