2010-02-08 11 views
12

Ciò accade la metà del tempo in cui si chiude la mia applicazione in cui ho inserito un TLMDHiTimer sul mio modulo in fase di progettazione, Abilitato su true. Nel mio evento OnFormClose, chiamo MyLMDHiTimer.Enabled: = false. Quando viene chiamato, a volte (circa la metà delle volte) ottengo questa eccezione.Che cos'è TExternalThread? "Impossibile terminare un thread creato esternamente" quando si termina un timer basato su thread

Ho eseguito il debug e sono entrato nella chiamata e ho rilevato che è la riga 246 in LMDTimer.pas a dare questo errore.

FThread.Terminate; 

Sto usando l'ultima versione di LMDTools. Ho fatto una reinstallazione completa degli strumenti LMD prima del fine settimana e ho rimosso e aggiunto nuovamente il componente al modulo correttamente.

Da quello che ho trovato, questo ha qualcosa a che fare con TExternalThread, ma non c'è documentazione su di esso da Embarcadero e non ho trovato nulla che faccia riferimento al codice sorgente LMDTools.

Uso completamente aggiornato RAD Studio 2010, Delphi 2010.

Ciò che mi sconvolge è che non c'è nessuna documentazione di sorta. Google yeilds un risultato di che effettivamente ne parla, in cui qualcuno dice che l'errore è causato dal tentativo di terminare un TExternalThread. Ma guardando il codice sorgente di questo LMDHiTimer, non una volta mira a fare altro che creare un TThread regolare. Il risultato di un google che ho trovato, Thread: Cannot terminate an externally created thread? su Embarcadero menziona utilizzando GetCurrentThread() e GetCurrentThreadId() per ottenere i dati necessari per collegarsi a un thread esistente, ma TLMDHiTimer non fa nulla del genere. Crea semplicemente il proprio discendente TThread con il proprio costruttore Create() (sovrascritto, ovviamente, e le chiamate ereditate all'inizio del costruttore)

Quindi ... Che diavolo è questo TExternalThread? Qualcun altro si imbatte in questo tipo di eccezione? E forse hai trovato una soluzione o una soluzione? Ho chiesto quasi la stessa identica domanda al supporto di LMDTools, ma non può far male chiedere in più posti.

Grazie in anticipo per qualsiasi assistenza.

risposta

11

TExternalThread avvolge una discussione che la RTL Delphi non ha creato. Potrebbe rappresentare un thread che appartiene al pool di thread del sistema operativo o forse un thread creato da un'altra DLL nel programma. Poiché il thread sta eseguendo il codice che non appartiene alla classe TExternalThread associata, il metodo Terminate non ha modo di notificare al thread che si desidera interrompere.

Un oggetto TThread Delphi imposterà la proprietà Terminated su True e il metodo Execute che viene sottoposto a override dovrebbe controllare periodicamente tale proprietà, ma poiché questo thread non è un codice Delphi, non esiste alcun metodo Execute e qualsiasi La proprietà terminata è entrata in vigore solo dopo il il codice del thread era già stato scritto altrove (non eseguendo l'override di Execute).

Il filo newsgroup suggerisce quello che sta probabilmente accadendo nel vostro caso:

memoria

... hai corrotto che causa il membro TThread.FExternalThread per diventare un valore diverso da zero.

Potrebbe essere dovuto a un bug nella libreria dei componenti, oppure potrebbe essere dovuto a un bug nel proprio codice.È possibile utilizzare i punti di interruzione del debugger per provare a scoprirlo. Imposta un punto di interruzione nel costruttore del thread del timer. Quando il programma si interrompe, utilizzare il comando "Aggiungi punto di interruzione" nel menu Esegui per aggiungere un punto di interruzione dati utilizzando l'indirizzo del campo FExternalThread del nuovo oggetto. Successivamente, se il valore di quel campo cambia, il debugger si fermerà e ti mostrerà cosa lo ha cambiato. (Il punto di interruzione dei dati verrà ripristinato ogni volta che si esegue il programma poiché l'IDE presuppone che l'oggetto non venga assegnato ogni volta allo stesso indirizzo.)

+0

Ho fatto come hai detto, ma l'unica volta che viene attivato questo punto di interruzione è la distruzione del componente alla chiusura dell'applicazione. Ho premuto MAIUSC + F7 per tracciare il passaggio successivo nel codice sorgente (utilizzando debug dcus, così posso vedere anche tutte le classi delphi sottostanti) e lo stack di chiamate appare come segue: http: // pastebin. org/88904 Mi sembra che stia semplicemente distruggendo i componenti del modulo. –

+0

Lo stack di chiamate visualizzato non è la distruzione del componente del timer. Sembra che sia la distruzione di qualche forma. Il campo FExternalThread era ancora False? Quale nuovo valore conteneva quella memoria quando il punto di interruzione dei dati è stato attivato? –

+0

Sì, sembra che sia la distruzione del modulo principale della mia applicazione. Il componente Timer è posizionato su di esso, il che mi fa interpretare lo stack di chiamata come il componente distrutto durante la distruzione del modulo (il modulo dovrebbe distruggere tutti i componenti su di esso durante la distruzione, giusto?) Sembra che quando valuto il valore di l'indirizzo di FExternalThread, si presenta sempre come vero, anche quando il debugger delphi stesso afferma che è falso. Scrivo @FExternalThread e faccio booleano ($ Addess) in valutazione e diventa 'vero' anche durante la creazione quando solo FExternalThread restituisce 'false'. –

0

Il problema è che FExternalThread non è inizializzato su falso per impostazione predefinita quando si Stai creando l'istanza TThread. Quindi non puoi garantire quale valore si trovi in ​​questa variabile (a volte può essere vero, a volte falso).

+3

-1 Sì, il membro 'FExternalThread' ** è ** inizializzato quando viene creato un oggetto' TThread'. Essendo un discendente 'TObject', ** tutti ** dei membri' TThread' sono inizializzati a zero su allocazione prima che venga chiamato il costruttore 'TThread'. Ciò significa che 'FExternalThread' è implicitamente inizializzato su False. L'unico modo in cui 'FExternalThread' può essere impostato su true è se viene creato un oggetto' TExternalThread' effettivo (ad esempio dal metodo di classe 'TThread.GetCurrentThread()') o se la memoria è danneggiata dal codice dell'app. –

5

C'è qualche possibilità che il codice stia provando a terminare un TThread già distrutto? Questo potrebbe facilmente accadere se hai impostato FreeOnTerminate.

Ho notato il tuo post durante la diagnosi di un errore simile (contrario?), "Impossibile chiamare Start su un thread in esecuzione o sospeso" nel costruttore di un componente inserito nel modulo principale. Quando ho rimosso Start(), quell'errore è stato sostituito da errori più eloquenti, ad es. "operazione puntatore non valida" e "violazione di accesso", nel distruttore corrispondente. Il componente stava cercando di manipolare il suo oggetto TThread dopo che TThread era stato liberato, lasciando così le cose alla legge di Murphy. Quando ho risolto ciò, sono stato in grado di sostituire la chiamata Start() senza l'errore "Impossibile richiamare l'avvio".

Per analogia, il problema potrebbe essere che l'indirizzo di FExternalThread era stato riciclato e scombussolato prima della chiamata distruttore/Terminare? Nel nostro caso, abbiamo implementato un buggy dello Singleton Instance Pattern; ma ancora, FreeOnTerminate sembra anche un probabile sospettato.

[FYI: Sto usando sto usando ++ C sotto RAD Studio XE]

0

Questo stesso problema mi ha spinto noci per diversi mesi. Ho approfondito il mio codice thread finchè non sono diventato quasi cieco.

Infine ho acquistato un buon sistema di eccezioni (EurekaLog) e mi ha indirizzato direttamente a un componente di terze parti che causava il problema. Questo componente, TAbLED, ha una proprietà per impostarlo a lampeggiare con una sorta di thread di temporizzazione. L'impostazione di non flash ha risolto il problema.

Problemi correlati