Threadende durch Callback realisieren
-
Hallo zusammen,
ich hab einen Mainthread der einen zweiten thread startet. Dieser soll nach getaner Arbeit dies dem Mainthread benachrichtigen. Das Problem dabei ist, dass der Mainthread nicht AKTIV auf diese benachrichtigung warten soll, sondern andere Dinge tun können.
Am Besten wäre es wenn der ausgelöste Event eine Funktion im Mainthread starten würde. Sprich sozusagen einen Callback auslöst... nur genau das bereitet mir Probleme.
Ich finde dies nicht im Internet und hoffe jemand kann mir da weiterhelfen. Alternative Möglichkeiten auch erwünschtMfg und danke
Tobi
-
Es gibt ja meistens die Möglichkeit irgendwelche Parameter an die Threadfunktion zu übergeben. Da könntest du z.B. den Funktionszeiger mit reinpacken.
-
hm.. na dann könnte ich aber nur die Funktion aus dem zweiten Thread aufrufen und nicht dem ersten Thread signalisieren dass er diese seine Funktion ausführen soll.
-
Wie soll der Mainthread denn auf die "Nachricht" reagieren?
-
Also der zweite Thread soll sein Ergebnis in eine globale Variable.. bzw. ein globaler Zeiger der darauf zeigt schreiben.
Der Mainthread soll dann nach der benachrichtigung diesen Zeiger auslesen mit dem Ergebnis und je nach aktuellem Stand des Programmes mit dem Ergebnis weiterverfahren.
tobi
-
Die einfachste Möglichkeit wird wohl sein, überall dort, wo der Main-Thread sinnvoll reagieren könnte, zu prüfen, ob das Ergebnis schon da ist (ggf. ein zusätzliches Flag verwenden, wenn es keinen passenden "NULL" Wert für das Ergebnis gibt). Für den Fall, dass dem Main-Thread langweilig wird, d.h. er doch aktiv warten muss, verwendest du eine Condition-Variable.
Und natürlich solltest du darauf achten an den nötigen Stellen zu synchronisieren.
-
also prinzipiell könnt das gehen was du sagst..
ich denke dabei gibt es nur ein weiteres Problem.
Es handelt sich um den Menueintrag "Geräte suchen" der nach anklicken einen weiteren Thread startet der eben über eine serielle Schnittstelle Geräte sucht...
dies dauert ca. 10 sekunden. Während dieser zeit soll es dem User ermöglicht werden wieder aus diesem Menupunkt herauszugehen und andere Menupunkte aufzurufen.
Hierzu erstmal die beiden Fälle die nun eintreten können.1. Der User geht aus dem Menupunkt "Geräte suchen" heraus und macht etwas anderes. Wenn er dann nach sagen wir 20 Sekunden wieder zurückkehrt kan ich einfach den globalen Pointer abfragen und das ergebnis wäre darin und könnte dann direkt in der Textbox angezeigt werden. -> Dies wäre der einfache Fall.
2. Der User wartet in diesem Menupunkt. Dies ist der Punkt der mir Probleme bereitet. Ich kann hier nicht aktiv den globalen Pointer abfragen weil sonst das ganze mainprogramm blockiert wäre falls der User doch aus dem Menupunkt herausgehen möchte. Dies würde dann nicht mehr gehen.
-> Irgendwie muss der zweite Thread also nach getaner Arbeit also nach ca. 10 Sekunden dem main Thread signalisieren das die ergebnisse da sind und er sie abrufen kann wenn er möchte...
Im Pseudocode wäre dies wie folgt:-------Mainthread---------
- Button OK wird für Menupunkt "Geräte suchen gedrückt
- ENTWEDER globaler Pointer entält Daten
- schreibe diese in die TExtbox
- setzte Inhalt des globalen Pointer auf NULL
- ODER
- createThread(Null,0,Thread2,0,0,int)
- zeige in der textbox an "Suchen..."
- mache evtl noch etwas ?!?!?!?!- Notifyfunktion
- aktualisiere Textbox mit Ergebnissen wenn user im Menupunkt "Geräte suchen" ist.
- setzte Inhalt des globalen Pointer auf NULL-------Thread2----------
- Suche Geräte Algorithmus (unwesentlich für dieses Thema)
- benachrichtige Mainthread über Ende dieses Threads und rufe dessen Notififunktion aufUnd genau dieses letzte Notify finde ich nicht heraus wie ich das realisieren kann.
Eine Conditionvariable und eine synchronisation ist denke ich weniger wichtig da diese globalen ergebnisse weniger atomar sind.. dann sind eben mal keine ergebnisse vorhanden obwohl der Thread2 gerade dabei wäre welche reinzuschreiben.. dann muss der User eben nen paar Sekunden später nochmal nachschauenAlso das muss doch so machbar sein aber ich finde einfach nix dazu
danke für eure Hilfe...
-
sprich.. wie kann ich PASSIV auf das Ende eines Threads warten..
-
Mit nem Event kannst du passiv warten
WinAPI:
WaitForSinleObject
bzw.
WaitForMultipleObjects
(du wartest ja darauf, dass das suchen fertig ist ODER der user ne eingabe macht)und
Eventsdie (bessere) Alternative ist www.boost.org
bb
-
Wobei doch WaitforSingleObject wieder meinen Mainthread blockiert und somit keine Buttonklicks möglich wären!?!?!
-
Guest33 schrieb:
Wobei doch WaitforSingleObject wieder meinen Mainthread blockiert und somit keine Buttonklicks möglich wären!?!?!
du musst doch nicht unendlich lange warten... so lange du das zeitinterval rel. klein hältst sollte der user da nichts merken...
außerdem hab ich dir auch noch waitformultipleobjects gegeben - wenn du es richtig sauber lösen willst brauchste eben 3 threads...
aber ich würds mit waitforsingleobject machen und immer nur 0,5sekunden oder so warten und das ganze in ner schleife laufen lassen, bis der request fertig bearbeitet ist...bb
-
danke für deine Antwort unskilled...
jedoch blockiert dann meine Schleife die das waitforsingleobject enthält oder nicht?
Wenn der User auf einen Button klickt dann geht ds nicht weil der Mainthread beschäftigt ist mit der Schleife...Ich bin inzwischen mal so weit und denke das das vielleicht geht. Der Grundgedanke steckt darin dass ich mit einem Invoke am ende des Threads die Textbox des Mainthreads verändern kann. Hierzu gebe ich meinen KLassenpointer (also this) dem Thread als Paraeter mit. Joch hab ich noch ein paar Fehlermeldungen mit denen ich nicht klar komme.
Hier mal der Code:#pragma once #include <windows.h> namespace dele { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; using namespace System::Threading; /// <summary> /// Zusammenfassung für Form1 /// /// Warnung: Wenn Sie den Namen dieser Klasse ändern, müssen Sie auch /// die Ressourcendateiname-Eigenschaft für das Tool zur Kompilierung verwalteter Ressourcen ändern, /// das allen RESX-Dateien zugewiesen ist, von denen diese Klasse abhängt. /// Anderenfalls können die Designer nicht korrekt mit den lokalisierten Ressourcen /// arbeiten, die diesem Formular zugewiesen sind. /// </summary> public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); // //TODO: Konstruktorcode hier hinzufügen. // } delegate void writeText( String^ myString ); writeText^ myDelegate; //void writeTextMethod( String^ myString ); //DWORD WINAPI ThreadFunction(LPVOID lpParam); //void notify(); typedef void (Form1::*MyFunctionPointer)(); //---------41 MyFunctionPointer functionPointer; protected: /// <summary> /// Verwendete Ressourcen bereinigen. /// </summary> ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::Button^ button1; System::Windows::Forms::TextBox^ textBox1; Thread^ myThread; System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// <summary> /// Erforderliche Methode für die Designerunterstützung. /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. /// </summary> void InitializeComponent(void) { this->SuspendLayout(); // // Form1 // this->ClientSize = System::Drawing::Size(292, 266); this->Name = L"Form1"; this->ResumeLayout(false); } #pragma endregion private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { DWORD threadId = 0; //functionPointer = &Form1::mynotify; HANDLE hThread = CreateThread(NULL,0,&(LPTHREAD_START_ROUTINE)ThreadFunction,this,0, &threadId); // ----- 83 //TSK_create(ThreadFunction,Form1::notify); //myThread = gcnew Thread( gcnew ThreadStart( this, &Form1::ThreadFunction ) ); //myThread->Start(); } void Form1::writeTextMethod( String^ myString ) { textBox1->Text = myString; } void mynotify() { String^ myString = "Invoketest"; Form1^ Form2 = this; array<Object^>^myStringArray = {myString}; Form2->Invoke( Form2->myDelegate, myStringArray ); } void ThreadFunction(LPVOID lpParam) { // TODO: MACHE ARBEIT // UND DANACH BENACHRICHTIGE MAINTHREAD ÜBER NOTIFYFUNKTION DER IN lpPARAM steht //void mynotify() = *(void*)lpParam(); //mynotify(); Form1 myForm = (Form1*)lpParam; // ------ 112 functionPointer = &Form1::mynotify; (myForm.*functionPointer)(); } }; }
und folgende fehlermeldungen:
Form1.h(41) : error C2843: 'dele::Form1': Die Adresse eines nicht statischen Datenmembers oder einer Methode eines verwalteten Typs kann nicht übernommen werden
Form1.h(83) : error C2440: 'Typumwandlung': 'overloaded-function' kann nicht in 'LPTHREAD_START_ROUTINE' konvertiert werden
Form1.h(112) : error C3699: "": Diese Referenzierung kann nicht für den Typ "dele::Form1" verwendet werden.
1> Der Compiler ersetzt "" durch ^", um die Analyse fortzusetzen.
Form1.h(112) : error C2440: 'Typumwandlung': 'LPVOID' kann nicht in 'dele::Form1 ^' konvertiert werden
1> Es ist kein benutzerdefinierter Konvertierungsoperator verfügbar, oder
1> Ein nicht verwalteter Typ kann nicht in einen verwalteten Typ umgewandelt werdenIch hoffe ihr könnt mir da weiterhelfen denn im Prinzip müsste es damit funktionieren.. ich hab euch um die Fehlerzeilen zu finden diese angedeutet im Code..
vielen Dank für eure Hilfe..
-
Bei Windows Forms kannst du z.B. BeginInvoke auf ein Control verwenden.
Mit "rohem" Win32 könntest du PostMessage verwenden.p.S.: im Endeffekt machen PostMessage bzw. BeginInvoke auch nix anderes als "an bestimmten Punkten abfragen obs was zu tun gibt" -- bloss macht das dann Windows bzw. das .NET Framework für dich.
-
danke für deine Antowrt Hustbaer...
ich denke mit PostMessage komme ich ned weitr soviel ioch darüber gelesen habe uind Begininvokeist doch so ähnlich wie das Invoke das ich drin habe.. bzw. was spricht gegen das Invoke?
Ich bin inzwischen so weit:#pragma once #include <windows.h> void ThreadFunction(LPVOID lpParam); void test(void *ptr); using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; using namespace System::Threading; /// <summary> /// Zusammenfassung für Form1 /// /// Warnung: Wenn Sie den Namen dieser Klasse ändern, müssen Sie auch /// die Ressourcendateiname-Eigenschaft für das Tool zur Kompilierung verwalteter Ressourcen ändern, /// das allen RESX-Dateien zugewiesen ist, von denen diese Klasse abhängt. /// Anderenfalls können die Designer nicht korrekt mit den lokalisierten Ressourcen /// arbeiten, die diesem Formular zugewiesen sind. /// </summary> public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } private: System::Windows::Forms::Button^ button2; public: private: System::Windows::Forms::TextBox^ textBox2; delegate void writeText( String^ myString ); writeText^ myDelegate; //void writeTextMethod( String^ myString ); //DWORD WINAPI ThreadFunction(LPVOID lpParam); //static void mynotify(); protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::Button^ button1; System::Windows::Forms::TextBox^ textBox1; Thread^ myThread; System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// <summary> /// Erforderliche Methode für die Designerunterstützung. /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. /// </summary> void InitializeComponent(void) { this->button2 = (gcnew System::Windows::Forms::Button()); this->textBox2 = (gcnew System::Windows::Forms::TextBox()); this->SuspendLayout(); // // button2 // this->button2->Location = System::Drawing::Point(81, 135); this->button2->Name = L"button2"; this->button2->Size = System::Drawing::Size(125, 36); this->button2->TabIndex = 0; this->button2->Text = L"button2"; this->button2->UseVisualStyleBackColor = true; this->button2->Click += gcnew System::EventHandler(this, &Form1::button2_Click); // // textBox2 // this->textBox2->Location = System::Drawing::Point(75, 61); this->textBox2->Name = L"textBox2"; this->textBox2->Size = System::Drawing::Size(144, 20); this->textBox2->TabIndex = 1; // // Form1 // this->ClientSize = System::Drawing::Size(292, 266); this->Controls->Add(this->textBox2); this->Controls->Add(this->button2); this->Name = L"Form1"; this->ResumeLayout(false); this->PerformLayout(); } #pragma endregion void Form1::writeTextMethod( String^ myString ) { textBox2->Text = myString; } void mynotify() { String^ myString = "Invoketest"; Form1^ Form2 = this; array<Object^>^myStringArray = {myString}; Form2->Invoke( Form2->myDelegate, myStringArray ); } private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { DWORD threadId = 0; //functionPointer = &Form1::mynotify; Form1^ Form2 = this; HANDLE hThread = CreateThread(NULL,0,&(LPTHREAD_START_ROUTINE)ThreadFunction,(void*)&Form2,0, &threadId); //TSK_create(ThreadFunction,Form1::notify); //myThread = gcnew Thread( gcnew ThreadStart( this, &Form1::ThreadFunction ) ); //myThread->Start(); } }; typedef void (Form1::*MyFunctionPointer)(); MyFunctionPointer functionPointer; void ThreadFunction(LPVOID lpParam) { // TODO: MACHE ARBEIT // UND DANACH BENACHRICHTIGE MAINTHREAD ÜBER NOTIFYFUNKTION DER IN lpPARAM steht //void mynotify() = *(void*)lpParam(); //mynotify(); Form1^ myForm = (Form1^)*lpParam; functionPointer = &Form1::mynotify; (myForm.*functionPointer)(); }
mit diesen Fehlermeldungen:
Form1.h(123) : error C2843: 'Form1': Die Adresse eines nicht statischen Datenmembers oder einer Methode eines verwalteten Typs kann nicht übernommen werden
Form1.h(134) : error C2100: Ungültige Dereferenzierung.
Form1.h(134) : error C2440: 'Typumwandlung': 'LPVOID' kann nicht in 'Form1 ^' konvertiert werden
1> Es ist kein benutzerdefinierter Konvertierungsoperator verfügbar, oder
1> Ein nicht verwalteter Typ kann nicht in einen verwalteten Typ umgewandelt werdenForm1.h(135) : error C2248: "Form1::mynotify": Kein Zugriff auf private Member, dessen Deklaration in der Form1-Klasse erfolgte.
Form1.h(103): Siehe Deklaration von 'Form1::mynotify'
Form1.h(26): Siehe Deklaration von 'Form1'Form1.h(136) : error C2296: '.*': Ungültig, da der linke Operand vom Typ 'Form1 ^' ist
Ich bin mir auch nicht sicher ob ich das sinnvoll gelöst habe in Zeile 114 das und dann Form2 in Zeile 115 übergebe.. aber direkt this übergeben bringt folgenden Fehler:
Form1.h(115) : error C2664: 'CreateThread': Konvertierung des Parameters 4 von 'Form1 ^const ' in 'LPVOID' nicht möglichIn einer Konsolenanwendung funktioniert dieser Aufbau prinzipiell. dort kann ich dann einen Thread genau wie hier starten und mit hilfe des übergebenen Klassenzeiger und des Funktionspointer die Funktion mynotify aufrufen.
Über entspechenden verbesserte oder alternative Codezeilen wäre ich sehr dankbar weil ich so langsam einfach nicht mehr weiterkomme und schon alles mögliche probiert habe.
Ablauf soll doch einfach nur sein:
-Button drücken
-dieser startet thread
-mainthread bleibt im leerlauf für weitere benutzereingaben und thread arbeitet
-thread ruft am ende seiner arbeit die mynotify funktion auf die dann mit hilfe von Invoke die Textbox verändert.Danke für eure Hilfe..
-
Du solltest nicht so grausam .NET Framwework-Code mit WinAPI Code mischen. Verwende System.Threading.Thread.Start anstelle von CreateThread.
Und dann verleg die Thread-Funktion in die Klasse, dann kannst du "mynotify" schonmal problemlos aufrufen.
-
also ich bin jetzt ein wenig weiter.. ich weis jetzt das ich letztendlich für eine Art Callback einen Pointer auf einen Memberfunktion einer Managedklasse an meinen unmanaged Code übergeben muss.. nur funktioniert das nicht richtig..
Folgendes Ausgangsszenario...
Managed Klasse:
bios.h
ref class bios { private: public: Form^ myGui; bios::bios(Form^ myGuiParam); // hier wird ein this Pointer einer windows Form übergeben desshalb überhaupt managed klasse.. static void makenotify(); // auf diese funktion wird ein Pointer benötigt };
bios.cpp
bios::bios(Form^ myGuiParam) { myGui = myGuiParam; // "this" Pointer der Form1.h wird hier gehalten um von hier aus ansprechbar zu sein. } void bios::makenotify() { bios::myGui->mynotify(); // hier wird über gehaltenen this Pointer der Windows Form die mynotify-Funktion aufgerufen. } // UNMANAGED void TSK_create(DWORD WINAPI f(LPVOID param)) { DWORD threadId = 3; HANDLE hThread = CreateThread(NULL,0,f,0,0, &threadId); } DWORD WINAPI inquiry(LPVOID param) { //THread arbeitet // hier soll der Pointer auf die Memberfunktion benutzt werden und die Memberfunktion makenotify aufgerufen werden die dann letztendlich die notifyfunktion der Form1 aufruft. return 0; }
Ich bekomme es einfach nicht hin dies Memberpointer zu realisieren.. ich denke ich brauche ein Delegate .. einen Marshal der mir irgendwie den Pointer der memberfunktion bereitstellt... aber ich bekomms einfach ned hin.. ich wäre sehr froh über ein paar Codestücke die den Code oben erweitern..
achja.. TSK_create kann ich nicht in die Klasse aufnehmen weil es die konvention einer anderen cpp datei nicht erlaubt das ich von dort eine memberfunktion dieser KLasse hier aufrufen kann. ich kann dort nur TSK_create() aufrufen und keine Klassen berücksichtigen.
mfg Guest