Un cliente mi ha chiesto di scoprire perché la loro applicazione C# (la chiameremo XXX, consegnata da un consulente che è fuggito dalla scena) è così squilibrata e la risolve. L'applicazione controlla un dispositivo di misurazione su una connessione seriale. A volte il dispositivo fornisce letture continue (che vengono visualizzate sullo schermo) e talvolta l'app deve interrompere le misurazioni continue e passare alla modalità comando-risposta.C# - Come consegnare quale thread legge dalla porta seriale?
Come NON per farlo
Per misurazioni continue, XXX utilizza System.Timers.Timer
per il fondo trattamento di ingresso seriale. Quando il timer scatta, C# esegue il timer ElapsedEventHandler
usando del filo dal suo pool. Il gestore di eventi di XXX utilizza un blocco commPort.ReadLine()
con un secondo timeout, quindi richiama un delegato quando arriva una misura utile sulla porta seriale. Questa porzione funziona bene, tuttavia ...
Quando è il momento di interrompere le misurazioni in tempo reale e di comandare al dispositivo di fare qualcosa di diverso, l'applicazione tenta di sospendere l'elaborazione in background dal thread della GUI impostando il timer Enabled = false
. Ovviamente, questo imposta semplicemente un flag che impedisce ulteriori eventi, e un thread in background che attende l'input seriale continua ad attendere. Il thread della GUI invia quindi un comando al dispositivo e tenta di leggere la risposta, ma la risposta viene ricevuta dal thread in background. Ora il thread di sfondo diventa confuso poiché non è la misura prevista. Nel frattempo, il thread GUI diventa confuso poiché non ha ricevuto la risposta del comando prevista. Ora sappiamo perché XXX è così friabile.
Metodo Possibile 1
In un'altra applicazione simile, ho usato un filo System.ComponentModel.BackgroundWorker
per misure free-running. Per sospendere l'elaborazione in background Ho fatto due cose nel thread GUI:
- chiamata al metodo
CancelAsync
sul filo, e - chiamata
commPort.DiscardInBuffer()
, che provoca una in sospeso (bloccato, in attesa) Comport lette in thread in background per gettare unSystem.IO.IOException "The I/O operation has been aborted because of either a thread exit or an application request.\r\n"
.
Nel thread in background rilevo questa eccezione e pulisco prontamente, e tutto funziona come previsto. Sfortunatamente DiscardInBuffer
provocare l'eccezione nella lettura del blocco di un altro thread non è un comportamento documentato ovunque riesca a trovarlo, e odio affidarmi a comportamenti non documentati. Funziona perché internamente DiscardInBuffer
chiama PurgeComm API Win32, che interrompe la lettura del blocco (comportamento documentato).
Metodo Possibile 2
Direttamente utilizzare il metodo BaseClass Stream.ReadAsync
, con un token di cancellazione monitor, usando un metodo supportato di interrompere sfondo IO.
Poiché il numero di caratteri da ricevere è variabile (terminato da una nuova riga) e non esiste un metodo ReadAsyncLine
nel framework, non so se questo è possibile. Potrei elaborare ogni personaggio singolarmente ma prenderebbe un risultato in termini di prestazioni (potrebbe non funzionare su macchine lente, a meno che, naturalmente, il bit di terminazione di linea sia già implementato in C# all'interno del framework).
metodo possibile 3
Creare un blocco "Ho la porta seriale". Nessuno legge, scrive o scarta l'input dalla porta a meno che non abbia il blocco (inclusa la ripetizione del blocco letto nel thread in background). Taglia i valori di timeout nel thread in background a 1/4 secondo per una reattività GUI accettabile senza troppo sovraccarico.
Domanda
Qualcuno ha una soluzione collaudata per affrontare questo problema? Come si può arrestare in modo pulito l'elaborazione in background della porta seriale? Ho cercato su Google e letto dozzine di articoli lamentando la classe C# SerialPort
, ma non ho trovato una buona soluzione.
Grazie in anticipo!
Non stanno concentrando sul problema reale, è System.Timers.Timer. Sbarazzati di esso e usa invece un timer sincrono. –
Mi dispiace Hans, non seguo. Nessuno dei possibili metodi 1-3 utilizza System.Timers.Timer; cosa stai suggerendo? –