none
CAsyncSocket in Threads benützen? RRS feed

  • Frage

  • ein problem, daß anscheinend vor langer zeit schonmal jemand hatte, worauf ich inzwischen leider auch gestoßen bin, aber keine antwort gefunden habe. gibt es hier vielleicht jemanden, der weiterhelfen kann?

    vgl: http://www.wer-weiss-was.de/theme158/article2128020.html

    Zitat:

    Ich möchte eine Socketverbindung in einem Client aufbauen. Stelle ich die Verbindung im normalen Hauptthread in dem meine Dialog läuft funktioniert es einwandfrei. (Habe da einen Button wenn der angeklickt wird wird die Verbindung aufgebaut).

    Jetzt will ich aber in einem eigenen Thread diese Verbindung aufbauen, da ich z.b. erst bei bestimmten ereignissen oder nach einer gewissen Zeit dies machen will. In dem fall kann ich ganz normal die Verbindung aufbauen, es werden dann aber keine Funktionen der von CAsyncSocket abgeleiteten Klasse aufgerufen (OnConnect, OnReceive) so das ich keine Daten empfangen kann.

    das senden funktioniert

    micha

    Donnerstag, 15. Juli 2010 12:17

Antworten

  • Hallo suriel6666!

    ok, zumindest scheine ich jetzt (endlich) verstanden zu haben, was du mir
    von anfang an mitteilen wolltest: nicht der thread, in dem ich mich eh
    schon befinde, ist das problem sondern daß ich die den socken benutzende
    dll innerhalb dieses threads benutze und nicht noch einen zusätzlichen
    dafür spendieren.

    Ich bin mir wirklich nicht sicher ob Du es verstanden hast. Deine letzte Anmkerung macht mich stutzig.

    Nochmal:
    1. Wenn Du einen STA hast musst Du in diesem eine Message Loop haben.
    2. Diese Message Loop muß laufen, wenn andere Threads auf COM Objekte in diesem STA zugreifen.
    3. Objekte in diesem STA können von andere Threads nur verwendet werden wenn das Thread Interface gemarshaled wird.
    4. Was für ein Appartment Du hast entscheidet sich mit dem Aufruf von CoInitializeEx!
    5. Eine DLL hat keinen Thread Context.
    6. D.h. weiterhin, wenn Du eine Funktion in einer DLL aufruft, die irgendwas mit Nachrichten macht, dann muss dieser Aufruf eben auch in einem Thread passieren, der eine Message-Loop hat, die auch "läuft"!

    Wenn Du also den I/O in einen eigenen Thread auslagerst der eine Message Loop fährt (und evtl. sonst nichts anderes macht). Dann kannst Du über Events/Flags auf die Ereignisse dieses Threads warten.


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Montag, 26. Juli 2010 06:16
    Moderator

Alle Antworten

  • Hallo suriel6666!

    Jetzt will ich aber in einem eigenen Thread diese Verbindung aufbauen, da ich z.b. erst bei bestimmten ereignissen oder nach einer gewissen Zeit dies machen will. In dem fall kann ich ganz normal die Verbindung aufbauen, es werden dann aber keine Funktionen der von CAsyncSocket abgeleiteten Klasse aufgerufen (OnConnect, OnReceive) so das ich keine Daten empfangen kann.

    Du musst einen CWinThread verwenden! da die Callbacks über Windows Messages (WM_SOCKET_NOTIFY) geschickt werden...


    Jochen Kalmbach (MVP VC++)
    Donnerstag, 15. Juli 2010 12:35
  • CWinThread wird immer benutzt auch bei AfxBeginThread, wichtiger ist, dass der Thread auch CWinThread::Run ausführt, was i.A. nur dann passiert wenn auch die AfxBeginThread Version mit CRuntimeClass benutzt wird.


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Donnerstag, 15. Juli 2010 13:11
    Moderator
  • Hallo Martin!

    CWinThread wird immer benutzt

    Aber bei CreateThread oder beginthread nicht, oder?


    Jochen Kalmbach (MVP VC++)
    Donnerstag, 15. Juli 2010 13:27
  • > Aber bei CreateThread oder beginthread nicht, oder?

    Nein! Dort nicht.
    Hier müsste man seine eigene Messageloop bauen. Was sich aber theoretisch schon wieder verbietet, Threads in der MFC ohne AfxBeginThread zu verwenden... (siehe Artikel in meinem Blog)


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Donnerstag, 15. Juli 2010 14:50
    Moderator
  • CWinThread wird immer benutzt auch bei AfxBeginThread, wichtiger ist, dass der Thread auch CWinThread::Run ausführt, was i.A. nur dann passiert wenn auch die AfxBeginThread Version mit CRuntimeClass benutzt wird.


    der thread wird bei mir per timeSetEvent gestartet. erfüllt er dann die gestellten kriterien? naja, offensichtlich nicht, oder?

    habe gerade gesehen, daß timeSetEvent veraltet ist und durch CreateTimerQueueTimer ersetzt werden soll. würde das abhilfe schaffen?

    falls ich timeSetEvent komplett erstzen muß (denke, das könnte klappen, da wird nix zyklisch gemacht), habt ihr ein beispiel oder ein link, wie man es richtig macht?

    Donnerstag, 15. Juli 2010 19:29
  • Nein! Das erfüllt in keiner Weise die Kriterien für die MFC! Du hast damit keinen kontrollierten Threadcontext und Du weißt nicht mal ob in diesem Thread eine Messageloop läuft...

    Also müsstest Du selbst einen neuen Thread starten...


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Donnerstag, 15. Juli 2010 19:50
    Moderator
  • Also müsstest Du selbst einen neuen Thread starten...

    ok, genau das hab ich getan, d.h. zumindest hab ichs mal versucht:
    - Klasse ableiten von CWinThread
    - DECLARE_DYNCREATE, InitInstance, ExitInstance() dazu
    - Thread statt per timeSetEvent jetzt per AfxBeginThread (mit CRuntimeClass) als suspended erzeugen, Parameter setzen und Resume rufen

    soweit scheint das grundsätzlich zu funktionieren, nur, wo schreibe ich den jetzt den Code rein, der vorher in dem Callback für timeSetEvent stand? also den Code, der im Grunde das tut, was der Thread machen soll

    Hintergrund:
    das komplette programm (gui), macht im grunde nix, außer ein leeres fenster anzeigen und ein vbscript zu starten (per timeSetEvent) und die programmkontrolle dorthin abzugeben:
    void startThread(){
    	timeSetEvent(1, 0, TimerXFkt,(unsigned long)&Threadpara, TIME_ONESHOT);
    }
    
    void CALLBACK TimerXFkt(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
    {
    	IActiveScript* m_Engine;
    	
    	//viel initialisierungszeugs mit m_Engine
    	
    	//Script starten, Kontrolle an Script abgeben
    	m_Engine->SetScriptState( SCRIPTSTATE_CONNECTED );
    	
    	//hier weiter, wenn Script beendet wurde
    }
    
    
    durch die SetScriptState-Zeile wird die Porgrammkontrolle ans vbscript-übergeben, von wo aus wiederum das gui (per com) angesteuert wird (dialoge anzeigen, daten verwalten, benutzereingabe usw.)
    in der zeile danach landet man erst wieder, wenn das script beendet wird, was erst beim beenden des programms der fall ist.

    Freitag, 16. Juli 2010 07:51
  • soweit scheint das grundsätzlich zu funktionieren, nur, wo schreibe ich den jetzt den Code rein, der vorher in dem Callback für timeSetEvent stand? also den Code, der im Grunde das tut, was der Thread machen soll

    Hintergrund:
    das komplette programm (gui), macht im grunde nix, außer ein leeres fenster anzeigen und ein vbscript zu starten (per timeSetEvent) und die programmkontrolle dorthin abzugeben:

    Einfach in InitInstance. Also dort Fenster erzeugen und m_pMainFRame setzen... Das ist wie ein kleines eigenständiges Programm! Wenn Du TRUE in InitInstance zurück gibst, fängt Run an zu laufen bis das m_pMainFRame beendet wird, oder PostQuitMessage ankommt...

    m_Engine->SetScriptState

    Achtung: Du darfst einen COM Zeiger nicht einfach in einem anderen Thread benutzen. Du musst diesen Marshallen.


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Freitag, 16. Juli 2010 08:40
    Moderator
  • Einfach in InitInstance. Also dort Fenster erzeugen und m_pMainFRame setzen... Das ist wie ein kleines eigenständiges Programm! Wenn Du TRUE in InitInstance zurück gibst, fängt Run an zu laufen bis das m_pMainFRame beendet wird, oder PostQuitMessage ankommt...

    hmpf... wenn nun aber wie gesagt SetScriptState garnicht zurückkommt, bis zum Programmende? mit anderen Worten InitInstance also auch nicht DURCHlaufen wird? Die Zeile nach SetSciptState wird erst erreicht, wenn das Script beendet wird.

    Achtung: Du darfst einen COM Zeiger nicht einfach in einem anderen Thread benutzen. Du musst diesen Marshallen.

    ich werde das mal untersuchen, danke für den hinweis
    Freitag, 16. Juli 2010 09:01
  • Was denn nun?
    Willst Du nun was asynchrones mit CAsynchSocket machen oder was?

    Was ist der Sinn von SetScriptState an dieser Stelle?

    Bzgl. marshaling von Interface Zeigern siehe GIT
    http://msdn.microsoft.com/en-us/library/ms693729(VS.85).aspx


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Freitag, 16. Juli 2010 09:10
    Moderator
  • > Willst Du nun was asynchrones mit CAsynchSocket machen oder was?

    mein programm startet mit setscriptstate ein vbscript, das mein programm steuert. dieses script läuft also im grunde in einer endlosschleife, die erst verlassen wird, wenn mein programm beendet wird. drum erreiche ich den code nach SetScriptState erst, wenn das Programm beendet wird.

    im laufe dieser steuerung durch das script, lädt mein programm ggf. eine drittanbieter-dll, welche per CAsycSocket daten aus dem ethernet holt.

    Die OnReceive-Methode dieses Sockets wird jedoch nicht aufgerufen (der ausführende thread ist ja der, der per timeSetEvent erzeugt wurde)

     

    Freitag, 16. Juli 2010 09:27
  • zu kryptisch formuliert oder niemand mehr eine idee dazu?
    Sonntag, 18. Juli 2010 16:27
  • Ja und? Du hast doch alle Informationen. Marshalle Deinen Interface-Zeiger über GIT undbenutze ihn. Was aber nicht heißt, dass es bei dem Aufruf evtl. zu einem Deadlock kommt.

    Aber mal Grundsätzlich. Wieso sollte der I/O Thread das Programm terminieren.
    Du könntest genauso ein Flag setzen der dann irgendwo aus dem Mainthread benutzt wird zu tun was Du willst.

    Was willst Du denn noch wissen?


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Sonntag, 18. Juli 2010 17:32
    Moderator
  • ich habe jetzt zumindest etwas, das funktioniert, würde dazu nur gern meinungen haben, ob das "sicher" funktioniert oder eher zufällig mal geklappt hat.

    zur architektur:
    gui startet wie gesagt per timeSetEvent einen neuen thread, der wiederum per IActiveScript::SetScriptState( SCRIPTSTATE_CONNECTED ) ein vbscript startet, daß die gesamte kontrolle des programms übernimmt. dieses script hat einen run-loop, aus dem es erst beim beenden wieder zurück kehrt. das script selbst steuert das gui und die logik/datenverarbeitung per com.     

    das problem war, daß CAsynSocket::OnReceive nicht aufgerufen wurde für Sockets, die ich innerhalb dieses vbscript-threads erzeuge/connecte

    da das script den run-loop nicht verlässt, hat auch ein starten des threads per AfxBeginThread(CRuntimeClass*) nichts gebracht (dessen CWinThread::Run wird ja nie aufgerufen, wenn ich aufgrund des endlos-loops im script CWinThread::InitInstance nie verlasse).               
           
    was nun abhilfe gebracht hat ist, einfach bei jedem loop-durchlauf des scripts eine com-methode aufzurufen, die für die messageverarbeitung zuständig ist (achtung pseudocode bzw. code ausm kopf):

    CMyDataServer::DoEvents(){<br/>
    	CWinApp* pApp = AfxGetApp();<br/>
    	if (pApp != NULL){<br/>
    		MSG msg;<br/>
    		while (::Peekmessage(msg, NULL, 0, 0, PM_REMOVE)){<br/>
    			pApp->PumpMessage();<br/>
    		}<br/>
    	}<br/>
    }             <br/>
    
    

    Dienstag, 20. Juli 2010 18:59
  • Und warm erzeugst Du die Sockets eben nicht gerade in einem anderen Thread?


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Mittwoch, 21. Juli 2010 10:58
    Moderator
  • > Und warm erzeugst Du die Sockets eben nicht gerade in einem anderen Thread?

    weil ich den socket selbst nicht in der eigenen hand habe, sondern dieser von einer dll erzeugt wird, die hier nur als binary da ist und von einem drittanbieter kommt.

    ich _könnte_ natürlich auch diese dll bereits im gui laden und das handle darauf herumreichen, aber das past nun so garnicht ins konzept, weil eben nur die logik weiß, ob und wann die dll angezogen werden muß oder nicht

    Mittwoch, 21. Juli 2010 16:01
  • Irgendwas verwechselst Du hier.
    Wenn Du eine DLL lädst, dann wird doch nicht jedes Handle/CAsynchSocket oder so in diesem Thread angelegt, der die DLL lädt.

    Du führst doch Befehle in der DLL aus, in einem Thread-Kontext!
    Wie kann eine "DLL" etwas ausführen? Antwort: Gar nicht! Das kann nur ein Thread! Wer ist dieser Thread? Wer führt den Code aus?


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Donnerstag, 22. Juli 2010 06:01
    Moderator
  • > Wenn Du eine DLL lädst, dann wird doch nicht jedes Handle/CAsynchSocket oder so in diesem

    > Thread angelegt, der die DLL lädt.

    dann verstehe ich das tatsächlich nicht. in welchen thread wird denn die dll per LoadLibrary geladen?

     

    > Du führst doch Befehle in der DLL aus, in einem Thread-Kontext!

    > Wie kann eine "DLL" etwas ausführen? Antwort: Gar nicht! Das kann nur ein Thread!

    > Wer ist dieser Thread? Wer führt den Code aus?

    bin mit nicht sicher, ob wir jetzt nicht aneinander vorbei reden.

    1. der vbscript-thread ruft per com eine methode in meinem server (c++) auf

    2. diese methode lädt die dll per LoadLibrary

    3. die so geladene dll, habe ich nur als binary

    4. die dll benutzt intern ein CAsyncSocket (das ausschließlich die dll private besitzt, also weder gebe ich das socket hinein, noch bekomme ich es hinaus) um Daten von der ethernet-schnittstelle einzusammeln

    5. als reaktion auf die OnReceive-Methode dieses Sockets wird von der dll ein callback in meinen server aufgerufen (diesen callback habe ich der dll natürlich vorher verraten)

    ein wenig klarheit geschaffen?

     

    was man bei allem natürlich nicht vergessen darf, ist ganz einfach folgendes:

    OnReceive des von der dll benutzten sockets (und mithin der callback in meinem code) wird nunmal nicht aufgerufen, wenn ich auf oben gepostetes PumpMessage verzichte.

    (das vbscript läuft in einem endlos-run-loop. genau in diesen loop packe ich einen aufruf nach PumpMessage)

    Donnerstag, 22. Juli 2010 10:48
  • dann verstehe ich das tatsächlich nicht. in welchen thread wird denn die
    dll per LoadLibrary geladen?

    Eine DLL wird nicht in einen Thread geladen. Sondern in einen Prozess.
    Jeder Thread kann Code aus der DLL nutzen.
     > bin mit nicht sicher, ob wir jetzt nicht aneinander vorbei reden.

    Nö. Du scheinst nur nicht zu vestehen, wer welchen Code wann ausführt und wer eine Message Loop braucht! ;)

    1. der vbscript-thread ruft per com eine methode in meinem server (c++) auf

    Und in weclhem Context ist dieses COM-Objekt erzeugt? Die Frage nach dem Appartment!

    2. diese methode lädt die dll per LoadLibrary

    Interessiert nicht, welcher Thread das ist.

    3. die so geladene dll, habe ich nur als binary

    Egal!

    4. die dll benutzt intern ein CAsyncSocket (das ausschließlich die dll
    private besitzt, also weder gebe ich das socket hinein, noch bekomme ich es
    hinaus) um Daten von der ethernet-schnittstelle einzusammeln

    Nein! Nicht die DLL benutzt dies, sondern eine Funktion, die Du aufrufst! Und dieser Thread Kontext ist es der evtl. eine Message Loop braucht.

    5. als reaktion auf die OnReceive-Methode dieses Sockets wird von der dll
    ein callback in meinen server aufgerufen (diesen callback habe ich der dll
    natürlich vorher verraten)

    Ja und?

    ein wenig klarheit geschaffen?

    Das war mir schon alles klar! ;)

    was man bei allem natürlich nicht vergessen darf, ist ganz einfach
    folgendes:

    OnReceive des von der dll benutzten sockets (und mithin der callback in
    meinem code) wird nunmal nicht aufgerufen, wenn ich auf oben gepostetes
    PumpMessage verzichte.

    Eben. Weil Du keinen neuen Thread startest und von dort aus die Anfrage durchführst. Das versuche ich Dir schon seit mehreren Postings zu sagen!

    (das vbscript läuft in einem endlos-run-loop. genau in diesen loop packe
    ich einen aufruf nach PumpMessage)

    Unwichtig... und der Ansatz ist IMHO falsch.

    Warum lässt Du den I/O im selben Thread laufen, wie das Script.

    - Script startet zweiten Thread
    - Script wartet auf Event oder auf Thread das Arbeit fertig ist.
    - Thread startet und benutzt Deine DLL.
    - Gleichzeitig läuft sie in Run hinein und pumped Nachrichten
    - Irgendwann ist der Jpob getan (Callback) und der Thread terminiert oder setzt ein Event für den Mainthread...

    Dadurch ist der I/O separiert in einem eigenen Thread und kann auch ohne Klimmzüge Nachrichten empfangen...

    PS: Das versuche ich Dir seit meiner ersten Antwort zu erklären... ;)


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Donnerstag, 22. Juli 2010 11:44
    Moderator
  • vorab danke für deine mühe/zeit

    > Du scheinst nur nicht zu vestehen, wer welchen Code wann ausführt und wer eine Message Loop braucht! ;)

    nunja.... sagen wir mal 70-80% des projektcodes sind nicht von mir und es gibt so gut wie keine dokumentation dazu... es gibt das sicher mehr als eine stelle, die funktioniert (oder auch nicht), ohne daß ich genau wüsste, warum, das stimmt wohl leider, ja

     

    > Und in weclhem Context ist dieses COM-Objekt erzeugt? Die Frage nach dem Appartment!

    tja... hierbei geh ich davon aus, daß es sich um single threaded apartment handelt bzw. daß es zumindest mal so sein sollte, aber ich WEIß es tatsächlich nicht zu 100% ;-(

     

    > Und dieser Thread Kontext ist es der evtl. eine Message Loop braucht.

    zumindest hier bin ich mir einigermaßen sicher, daß er die message loop nicht nur evt. braucht, sondern braucht.

    um nun die bestehende architektur, wie gut oder schlecht sie auch sein mag, zumindest läuft sie seit jahren beim kunden, möglichst nicht zu verändern, kam nun der gedanke, zyklisch per pumpmessage anstehende messages zu verarbeiten, statt sich mich einem neuen thread "auseinanderzusetzen". zumindest scheint es ja zu funktionieren

     

    > Ja und?

    nichts "und" - fyi only

     

    > Eben. Weil Du keinen neuen Thread startest und von dort aus die Anfrage durchführst.

    ok, wie es also aussieht, scheint es (mindestens) 2 möglichkeiten zu geben:

    1. neuen thread starten

    2. in meinem thread bleiben und per pumpmessage vorhandene nachrichten abarbeiten lassen

    wo liegen denn nun die nachteile von 2.?

     

    ok, zumindest scheine ich jetzt (endlich) verstanden zu haben, was du mir von anfang an mitteilen wolltest: nicht der thread, in dem ich mich eh schon befinde, ist das problem sondern daß ich die den socken benutzende dll innerhalb dieses threads benutze und nicht noch einen zusätzlichen dafür spendieren.

    ich werde mal drüber nachdenken, ob bzw. wie das im bestehenden system zu realisieren ist. zu viel auseinanderrupfen möchte man ja nach möglichkeit eben auch nicht

     

    micha

    Sonntag, 25. Juli 2010 13:14
  • Hallo suriel6666!

    ok, zumindest scheine ich jetzt (endlich) verstanden zu haben, was du mir
    von anfang an mitteilen wolltest: nicht der thread, in dem ich mich eh
    schon befinde, ist das problem sondern daß ich die den socken benutzende
    dll innerhalb dieses threads benutze und nicht noch einen zusätzlichen
    dafür spendieren.

    Ich bin mir wirklich nicht sicher ob Du es verstanden hast. Deine letzte Anmkerung macht mich stutzig.

    Nochmal:
    1. Wenn Du einen STA hast musst Du in diesem eine Message Loop haben.
    2. Diese Message Loop muß laufen, wenn andere Threads auf COM Objekte in diesem STA zugreifen.
    3. Objekte in diesem STA können von andere Threads nur verwendet werden wenn das Thread Interface gemarshaled wird.
    4. Was für ein Appartment Du hast entscheidet sich mit dem Aufruf von CoInitializeEx!
    5. Eine DLL hat keinen Thread Context.
    6. D.h. weiterhin, wenn Du eine Funktion in einer DLL aufruft, die irgendwas mit Nachrichten macht, dann muss dieser Aufruf eben auch in einem Thread passieren, der eine Message-Loop hat, die auch "läuft"!

    Wenn Du also den I/O in einen eigenen Thread auslagerst der eine Message Loop fährt (und evtl. sonst nichts anderes macht). Dann kannst Du über Events/Flags auf die Ereignisse dieses Threads warten.


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Montag, 26. Juli 2010 06:16
    Moderator
  • > 1. Wenn Du einen STA hast musst Du in diesem eine Message Loop haben.

    ... mit anderen worten entweder den mfc-internen benutzen (siehe oben, CWinThread und initinstance durchlaufen) oder alternativ sowas wie "while (::peekmessage) pumpmessage" zyklisch selbst aufrufen (viel mehr tut ja CASyncSockt::Run auch nicht)?

     

    > 3. Objekte in diesem STA können von andere Threads nur verwendet werden wenn das Thread

    > Interface gemarshaled wird.

    ist (glaube ich) einfach nicht relevant, weil alle daten in dem einen thread gehalten werden und das gui per messages gesteuert wird

     

    > 4. Was für ein Appartment Du hast entscheidet sich mit dem Aufruf von CoInitializeEx!

    eine suche ergab gerade, daß es nirgens einen solchen aufruf gibt. stattdessen 1x "CoInitialize", was wiederum auf sta hindeutet, richtig?

     

    > 6.

    wie erstens.

    wenn es aber nunmal nicht so ist, daß mein thread eine message-loop hat, die auch läuft, wäre hier pumpmessage angebracht?

     

    Montag, 26. Juli 2010 08:23