TcpClient, Asynchrones lesen, BackgroundWorker ???



  • Hi,

    wie in einem anderen Thread schon erwähnt bin ich gerade dabei eine Clientanwendung mittels der Klasse TcpClient zu programmieren.
    Was ich bissher habe ist eine eigene Klasse TClient, die mir den ganzen Verbindungsaufbau, meine dazugehörigen Methoden, etc. verwaltet.
    Ich möchte nun, dass der Client, sobald eine Nachricht vom Server kommt, reagiert, die Nachricht bearbeitet bzw. einfach mal in ner Textbox ausgibt.

    Wie mach ich das...
    ... ich lege also einen Networkstream an und beginne einen Asynchronenlesevorgang, der, sobald eine Nachricht kommt, reagiert und diese entgegen nimmt...
    ... ich möchte aber weiter, dass mein System nicht blockiert während er auf ne Nachricht wartet (tut er das beim asynchronen lesen? oder liege ich hier schon falsch?)
    Ich bin dann auf die Klasse BackgroundWorker gestoßen...
    ... Ich starte nun also meinen BGWorker

    this->backgroundWorker1->RunWorkerAsync();
    

    ... dieser startet in seiner DoWork-Methode meine DoRead-Methode

    e->Result = WindowsClient::TClient::DoBeginRead();
    

    In dieser Methode startet mein asynchroner Lesevorgang... sobald eine Nachricht eintrifft gibt diese "true" zurück, was wiederum zu e->Result=true führt und der BGWorker startet seine Completed-Methode...
    Hier wird die empfangene Nachricht in der Textbox ausgegeben.

    Das ganze funktioniert soweit auch, allerdings bin ich mir nicht sicher ob der ganze Ablauf so auch sinnvoll ist, oder man das vielleicht auch geschickter lösen könnte?
    Zudem funktioniert das ganze auch nur einmal, ich müsste nun also sobald mein BGWorker durch ist, das ganze nochmal starten.
    Wie und vorallem wo mache ich das?
    Ich hab das Gefühl, der ganze Ablauf ist noch ein bisschen sehr "hingebastelt"... allerdings weiß ich im Moment noch keine bessere Lösung 😕

    Kennt sich jemand hiermit aus und kann mir ein paar Tipps geben?
    Ich bin für jede Anregung dankbar 🙂

    Gruß Tobi



  • ... ich möchte aber weiter, dass mein System nicht blockiert während er auf ne Nachricht wartet (tut er das beim asynchronen lesen? oder liege ich hier schon falsch?)

    Ja, Du liegst falsch. Bim asynchronen lesen wird nicht blockiert.

    Also:
    Entweder synchron read + bg worker
    Oder asynchron read



  • Hmm...
    ok funktioniert, dann hätten wir im anderen Thread bleiben können 🙂
    Problem war ich hatte WaitOne(); drin und das hat natürlich blockiert.

    Jetzt habe ich allerdings noch zwei Probleme.
    1.)
    Das ganze funktioniert nur einmal.
    Ich starte meinen Lesevorgang, sobald was kommt springt er in die Callback und nimmt das entgegen. Ich schicke vom Server nochmal eine Nachricht und er reagiert erst wieder wenn ich den asynch.Lesevorgang ein zweites mal starte.
    Geht es, dass er ständig schau ob etwas kommt?

    2.)
    Die ganzen Methoden für die Verbindung und das austauschen von Nachrichten stehen in einer seperaten Klasse. Ich kann also von dort aus nich auf meine Form zugreifen um die Nachricht auszugeben... würde ja auch keinen Sinn machen.
    Beim BgWorker hatte ich die Möglichkeit in BGWCompleted die Nachricht auszugeben, sobald eben was empfangen wurde.
    Da ich ich aber aus meiner Callback-Methode keinen Rücksprung in irgend eine Methode meiner Form habe, weiß ich im Moment nicht wie ich in meiner Form meine Nachricht anzeigen soll? Ich kann ja nich alle paar Min,sec. etc. mal Testen ob vllt was gekommen ist.
    Wie könnte ich das lösen?



    Mit BeginRead startest Du den Lesevorgang. In der Callback Routine rufst Du dann EndRead auf und dann musst Du erneut BeginRead aufrufen. Das ist IMO das typische APM (asychronous programming model).

    Entwerder Du implementierst ein Event und transportierst so die Daten od. Du implementierst den Daten austausch via ein Interface.

    Du musst darauf achten, dass die Callback Routine nicht auf dem UI Thread aufgerufen wird und Du bei einem Zugriff auf das GUI den Aufruf via Invoke (des Control's, bzw. der Form) auf den UI Thread marshallst.

    Gruss Simon



  • Gut 🙂
    Mit

    stream->BeginRead( myReadBuffer, 0, myReadBuffer->Length, gcnew AsyncCallback( &TClient::myReadCallBack ), stream);
    

    kann ich innerhalb meiner Callback, das asynch.Lesen erneut starten... das funktioniert auch soweit.
    Mein Gedanke war nur, dass es evtl. zu einer Art "Verschachtelung" führen könnte... da ich innerhalb meiner Callback-Methode das ganze nochmal starte, bevor die eigentliche Routine beendet wurde. Aber wenn das keine Probleme macht, lass ich das so.

    Die Übergabe versuch ich per Event... sollte eigentlich funktionieren.

    Was meinst du mit "UI Thread marshallst"

    Danke schon mal so weit 🙂



  • Du musst Aktionen die ein Control betreffen, auf dem Thread ausführen, aus dem das Control erstellt worden ist. Das ist meistens der Main Thread.

    Das ist aus historischen Gründen so (bei Windows).

    Um das zu realisieren, kannst Du die Invoke Methode, die jedes Control hat, benutzen.
    http://msdn2.microsoft.com/en-us/library/zyzhdc6b.aspx

    Hier ist ein Post (suche nach Invoke):
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-198995-and-highlight-is-.html

    Edit:
    Hier ist noch ein klares Bsp. (allerdings in C#):
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-206305-and-highlight-is-.html



  • hmm... so ganz funktionieren wills noch nicht.
    das eventhandling funktioniert so weit. nur eben das invoke hab ich noch nicht ganz verstanden

    Invoke muss an der Stelle aufgerufen werden wo meinem Event der Delegat mit der entsprechenden Behandlungs-Methode zugewiesen wird?

    //Mein Delegat:
    public delegate void MessageEventHandler(String^);	
    
    //Mein Event und die Methode in der entsprechenden Klasse TClient
    static event	MessageEventHandler^ MessageReceive;	
    static void	MessageCallback();
    
    // myCompleteMessage ist der entsprechnde String
    static String^ myCompleteMessage = "";
    
    //
    void	TClient::MessageCallback()
    {
    MessageReceive(myCompleteMessage);		
    }	
    
    // Hier den Delegaten zuweisen? und dann mein Asynchrones lesen starten
    private: System::Void buttonStart_Click(System::Object^  sender, System::EventArgs^  e) 
    {
    TcpHandling::TClient::MessageReceive+= this->Invoke(gcnew WindowsClient::MessageEventHandler(&Form1::GetMessage));
    TcpHandling::TClient::DoBeginRead();
    }
    
    //Eventbehandlung in Form1
    private: void GetMessage(String^ msg)		
    {
    (this->textBox1->Text = msg);
    }
    

    Im Moment gibts folgende Fehlermeldung. Wobei mein Delegat void MessageEventHandler(String^) doch mit void GetMessage(String^ msg) übereinstimmt?

    Fehler 2 error C3352: "void WindowsClient::Form1::GetMessage(System::String ^)": Die angegebene Funktion stimmt nicht mit dem Delegattyp "void (System::String ^)" überein. c:\dokumente und einstellungen\rosentht\eigene dateien\visual studio 2005\projects\windowsclient\windowsclient\Form1.h 388

    //edit
    jetzt kommt die Fehlermeldung auch ohne das Invoke... irgendwo muss wohl was falsch definiert sein



  • Invoke muss an der Stelle aufgerufen werden wo meinem Event der Delegat mit der entsprechenden Behandlungs-Methode zugewiesen wird?

    Nein.

    Ich versuche mal "hineinzuflicken":

    private: System::Void buttonStart_Click(System::Object^  sender, System::EventArgs^  e)
    {
       TcpHandling::TClient::MessageReceive+= gcnew WindowsClient::MessageEventHandler(&Form1::GetMessage));
       TcpHandling::TClient::DoBeginRead();
    }
    
    // Annahme, GetMessage ist eine nicht statische Memberfunktion von Form1.
    private: void Form1::GetMessage(String^ msg)       
    {
       if (this->textBox1->InvokeRequired)
       {
          MessageEventHandler^ invoker = gcnew MessageEventHandler(this, &Form1::GetMessage)
          array<Object^>^ args = { msg };   // Bin nicht sicher ob diese Art das Array zu instanzieren korrekt ist (kanns leider nicht testen).
          this->textBox1->Invoke(invoker, args);
       }
       else
       {
          this->textBox1->Text = msg;
       }
    }
    

    Ich denke nicht, dass das Event Handling (MessageReceive Event und MessageCallback(..)) statisch sein muss. Schaue wie ich den Delegaten in Form1::GetMessage erzeuge, dort wird für eine Instanz Methode noch der this Zeiger benötigt.

    Gruss Simon



  • Spitze, das funktioniert so 🙂

    Kann man also sage, ich schaue mit InvokeRequired ob der Thread, der aufruft nicht der Thread meines Steuerelements ist. Trifft das zu, übergebe ich quasi mit Invoke mein Steuerelement dem entsprechnenden Thread, bzw. ein Verweis auf mein Steuerelement?



  • Kann man also sage, ich schaue mit InvokeRequired ob der Thread, der aufruft nicht der Thread meines Steuerelements ist.

    Korrekt.

    übergebe ich quasi mit Invoke mein Steuerelement dem entsprechnenden Thread, bzw. ein Verweis auf mein Steuerelement?

    Nein. Sondern Du übergibst der Message Queue des GUI Threads einen Delegaten (in Form einer Message), der Aufgerufen werden soll, wenn die Message abgearbeitet wird. Dies geschieht dann auf dem GUI Thread.


Anmelden zum Antworten