SetCommMask() blockiert den Thread zum lesen der Daten von COM-Port
-
Hallo alle zusammen!
Zum Problem: Ich habe einen Thread erstellt zum lesen der ankommenden Daten (Strings) von COM-Port und habe feststgestellt, dass die Funktion SetCommMask() mein Thread blockiert. Es wirkt sich auf das Programm so aus als wäre dies kein Thread. Programm reagiert nicht mehr auf Mausklicks....
Hier ist der Code im Thread:
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "PortLesenThread.h" #include "Terminal.h" #include "ComPort_.h" #pragma package(smart_init) //--------------------------------------------------------------------------- // Wichtig: Objekt-Methoden und -Eigenschaften in der VCL dürfen nur // in einer Methode nur mir Synchronize aufgerufen werden, z.B.: // // Synchronize(&UpdateCaption); // // wobei UpdateCaption folgendermaßen aussehen könnte: // // void __fastcall TEmpfang::UpdateCaption() // { // Form1->Caption = "Aktualisiert in einem Thread"; // } //--------------------------------------------------------------------------- __fastcall TEmpfang::TEmpfang(bool CreateSuspended) : TThread(CreateSuspended) { //Priority = tpIdle; Fehler = 0; FreeOnTerminate = true; } //--------------------------------------------------------------------------- void __fastcall TEmpfang::Execute() { PortName = Form1->cb1->Text; TerminalF->Visible = true; Synchronize(&ComPortAuslesen); } //--------------------------------------------------------------------------- void __fastcall TEmpfang::ComPortAuslesen() { HANDLE hCom; BOOL bErfolg; DWORD iBytesRead; DWORD dwEventMask; DCB dcb; hCom = CreateFile (PortName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, // muss bei Exclusivzugang geoefnet werden NULL, // keine security Attributen OPEN_EXISTING, // OPEN_EXISTING muss verwendet werden 0, // nicht überlappend I/O NULL); // hTemplate must be NULL for comm devices if (hCom == INVALID_HANDLE_VALUE) { Fehler = 1; ShowMessage(FehlerString()); } bErfolg = GetCommState(hCom, &dcb); if (!bErfolg) { Fehler = 2; ShowMessage("COM Eintellungen können nicht abfragt werden: " + FehlerString()); } dcb.DCBlength = sizeof(DCB); // Laenge des Blockes MUSS gesetzt sein! dcb.BaudRate = 9600; // Baudrate dcb.ByteSize = 8; // Datenbits dcb.Parity = NOPARITY; // Parität dcb.StopBits = ONESTOPBIT; // 1 Stopbit bErfolg = SetCommState (hCom, &dcb); // COM-Einstellungen setzen if (!bErfolg) { Fehler = 3; ShowMessage("COM Einstellungen können nicht gesetzt werden: "+ FehlerString()); } /* SetCommMask() Funktion spezifiziert einen Satz für die Überwachung Ereignissen im Monitor. EV_RXCHAR bedeutet, dass ein Zeichen wurde empfangen und in den Eingangspuffer gelegt. */ // -- Empfangssignale definieren if(!SetCommMask(hCom, EV_RXCHAR | EV_ERR)) { Fehler = 3; ShowMessage("Empfangssignale können nicht definiert werden: "+ FehlerString()); } /* Die WaitCommEvent() Funktion wartet, dass ein Ereignis für ein spezifiziertes Gerät auftritt. Der Satz der Ereignisse, die durch diese Funktion überwacht werden, ist die enthaltene Schablone, die mit dem Handle auf Gerät verbunden ist. */ //if(WaitCommEvent(hCom, &dwEventMask, 0) && Fehler == 0 ) { char Byte = 0; DWORD dwBytesEmpfangen; Form1->szAnzeige = ""; // In Endlos-Schleife auf Empfangssignale warten: do { if(dwEventMask & EV_RXCHAR) { ReadFile (hCom, &Byte, sizeof(Byte), &dwBytesEmpfangen, 0); Form1->szAnzeige = Form1->szAnzeige + Byte; Application->ProcessMessages(); Synchronize(&Anzeigen); } else if(dwEventMask & EV_ERR) { Form1->szAnzeige = "Fehler"; break; // Schleifen-Abbruch } //ShowMessage(dwBytesEmpfangen); } while(dwBytesEmpfangen > 0); } CloseHandle(hCom); // COM schließen Fehler = 0; } //--------------------------------------------------------------------------- AnsiString TEmpfang::FehlerString() { LPVOID lpMsgBuf; // Zeiger auf Message-Buffer // Fehlermeldung auslesen und in lpMsgBuf schreiben: FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL); AnsiString slBuffer = (char *)lpMsgBuf; // in AnsiString konvertieren LocalFree(lpMsgBuf); // Buffer freigeben return slBuffer; // Meldung zurückgeben } //--------------------------------------------------------------------------- void __fastcall TEmpfang::Anzeigen() { TerminalF->Label1->Caption = ""; TerminalF->Label1->Caption = Form1->szAnzeige; //---------------------------------------------------------------------------
Kann jemand mir helfen?
Woran liegt dieses Verhalten?Für jede hilfe wäre ich sehr Dankbar!
-
aesse1 schrieb:
Es wirkt sich auf das Programm so aus als wäre dies kein Thread.
Synchronize führt ja dazu, das der Thread angehalten wird und die übergebene Funktion im Kontext der Oberfläche ausgeführt wird.
Der Code ist also so gemeint, das für jedes empfangene Zeichen in der GUI-Thread gewechselt wird. Also theoretisch bei der hier eingestellten Baudrate 9600 Mal pro Sekunde. Das ist ziemlich sinnlos.
Das SetCommMask / WaitCommEvent kann man sich auch sparen, ReadFile verhält sich bei seriellen Schnittstellen so, dass es eine Zeit lang wartet und bei einem Timeout mit 0 Bytes zurückkommt, wenn man mit SetCommTimouts sinnvolle Werte einstellt. Dann kann man auch ganze Zeilen am Stück lesen, statt jedes Zeichen einzeln ...
-
SetCommMask() blockiert keinen Thread, jedenfalls bei mir nicht. Bei deinem Codeschnipsel (mit auskommentiertem WaitCommEvent()) bleibt er erst in der while-Schleife zum Datenlesen hängen. Und zwar weil dwBytesEmpfangen uninitialisiert ist und daher auch ohne ankommende Daten ungleich 0 ist. dwEventMask ist übrigens auch uninitialisiert.
WaitCommEvent() blockiert einen Thread solange, bis ein definiertes Event auftritt. Auch deshalb muss das Warten auf Events und das Datenlesen in einen eigenen Thread.
-
Hallo alle zusammen,
Danke für Eure Antworten.
Morris! mein Code samt (das Warten auf Events und das Datenlesen) ist schon in einem eigenem Thread, und trotzdem das Hauptfenster wird blockiert, oder Du meinst es müssen noch zwei zusatzlichen Threads erzeugt werden?
-
Nein, dein Code wird aus dem Thread mittels Synchronize() aufgerufen. Damit wird er im Hauptthread ausgeführt. Du solltest Synchronize() nur zum Übermitteln der gelesenen Daten ans Formular verwenden. Ein Lesethread reicht für deine Zwecke.
Edit: verschiedenes
Edit2:
Noch ein Fehler, der zwar das aktuelle Problem nicht verursacht aber zu ähnlichem Verhalten führen könnte:BCB-Hilfe schrieb:
Warnung: Rufen Sie Synchronize nicht aus dem VCL-Haupt-Thread aus auf. Dies würde zu einer Endlosschleife führen.
Du rufst CommPortAuslesen() mittels Synchonize(), also im VCL-Haupt-Thread, auf und darin nochmal Synchronize(&Anzeigen). Das darfst du auch nicht.
-
Der Code ist auch unvollstängig. Man kann nicht ausschliessen, dass das ReadFile auch blockiert.
MSDN schrieb:
When reading from a communications device, the behavior of ReadFile is determined by the current communication time-out as set and retrieved by using the SetCommTimeouts and GetCommTimeouts functions. Unpredictable results can occur if you fail to set the time-out values. For more information about communication time-outs, see COMMTIMEOUTS.
Und wie schon gesagt, abhängig von den gesetzten Timeouts wartet ReadFile entweder bis Daten da sind oder die Zeit abgelaufen ist, oder es kommt sofort zurück. In beiden Fällen muss man prüfen, wie viele Bytes gelesen wurden.
Die Verwendung von WaitCommEvent ist in diesem Zusammenhang also völlig überflüssig.