SDK-Helfer/ Tools | | | | Jens-Arne Reumschüssel | Hier stelle ich eine Methode vor, Multithreading mit XProfan zu realisieren.
----- Hinweis: Das für diesen Artikel benötigte Zusatzprogramm Profan2Cpp gibt es hier: [...] -----
Ja, XProfan ist nicht multithreadingfähig, das stimmt und bleibt bis auf weiteres auch so. Es gibt aber einen Weg, dies mit reinem XProfan als Programmiersprache doch zu realisieren. Dieser führt über den Umweg, dass die Prozedur, die als eigener asynchroner Thread gestartet werden soll, mit Profan2Cpp in eine C-DLL ausgelagert wird. Dies wird hier an einem einfachen Beispiel erläutert, welches einfach nur in drei XProfan-Text-Controls einen vorgegebenen Wert um 10.000 hochzählt. Jeder Counter läuft in einem eigenen Thread, der sich danach beendet und dabei über sendmessage einen Rückgabewert (hier das Handle seines Text-Controls) an eine im aufrufenden Programm definierte usermessage übermittelt.
Multithreading bietet den unschätzbaren Vorteil, im "Hintergrund" Aufgaben erledigen zu können, während das Hauptprogramm weiterläuft, und dabei Handles und Speicherelemente austauschen zu können. XProfan ist zwar multiprozessingfähig, aber dabei läuft der neue Prozess in einem getrennten Adressraum, was die Rückgabe von neu angelegten Handles etc. verhindert. Multithreading hingegen bietet genau dies: einen gemeinsamen Adressraum von aufrufendem Programm und dem neuen Thread. Aber Achtung, dies birgt auch Risiken. Hierzu bitte unbedingt die Hinweise ganz unten unter den Code-Beispielen lesen! Diese sind bestimmt noch nicht vollständig, Ergänzungen sind gerne willkommen.
Beide Code-Beispiele sind intensiv kommentiert, sodass ich hoffe, dass man ohne Weiteres versteht, was dort geschieht.
Hier zunächst die XProfan-Prozedur, die mit Profan2Cpp in eine C-DLL übersetzt werden muss (bitte die Datei MultithreadingBeispielC.prf nennen und die erzeugte DLL in das Verzeichnis des XProfan-Hauptprogramms kopieren, das danach als Code-Beispiel folgt):
[Alternativ gibt's die fertige DLL auch hier, falls jemand mit Profan2Cpp im Moment nicht up to date ist: Herunterladen]
$DLL
DLLPROC Count_C,1,"Count_C"
parameters param#
declare i&,a&,s$,hAufrufer&,ThreadRunning#,hText&,StartWert&
s$=@string$(param#,0)
'als erstes das Flag für das aufrufende Programm setzen, dass der Thread läuft
a&=val(@substr$(s$,2,"²~³"))
ThreadRunning#=a&
long ThreadRunning#,0=1
'Aufrufer-Fensterhandle ermitteln
hAufrufer&=@val(@substr$(s$,1,"²~³"))
'Textcontrol-Handle ermitteln
hText&=@val(@substr$(s$,3,"²~³"))
'Startwert ermitteln
StartWert&=@val(@substr$(s$,4,"²~³"))
'Zählung durchführen
i&=StartWert&
while i&<=StartWert&+10000
settext hText&,@str$(i&)
inc i&
endwhile
'Rückgabewert übermitteln
@sendmessage(hAufrufer&,$1000,hText&,0)
long ThreadRunning#,0=0'Flag für das aufrufende Programm dafür, dass der Thread läuft, löschen
return 0
ENDPROC
Nun folgt das normale XProfan-Programm, das die drei Threads, die eingangs erwähnt wurden, aufruft und auf deren Ende wartet:
'Multithreading-Beispiel mit XProfan-ThreadProc in C-DLL über Profan2Cpp
$H windows.ph
declare hDLL%,uwp&,CProcAddr&
declare hText1&,hText2&,hText3&
declare s1$,s2$,s3$
declare hThread1&,hThread2&,hThread3&
declare ThreadRunning1&,ThreadRunning2&,ThreadRunning3&
cls ~getsyscolor(4)'Hauptfenster mit Hintergrundgrau erstellen (weil die Text-Dialogelemente in dieser Farbe gehalten sind)
usermessages $1000'mit dieser Message signalisiert ein Thread, dass er sich nun beendet (das kann zusätzlich mit den Flags ThreadRunningX& überprüft werden, was in diesem Beispiel aber nicht benutzt wird)
hText1&=@create("TEXT",%HWnd,"0",10,10,300,20)'hier wird Thread 1 reinschreiben
hText2&=@create("TEXT",%HWnd,"0",10,35,300,20)'hier wird Thread 2 reinschreiben
hText3&=@create("TEXT",%HWnd,"0",10,60,300,20)'hier wird Thread 3 reinschreiben
hDLL%=@usedll("MultithreadingBeispielC.dll")'in dieser DLL ist die Thread-Prozedur enthalten
'Threads starten
CProcAddr&=~GetProcAddress(hDLL%,"Count_C")'"echte" ProcAddr der in die DLL ausgelagerten Prozedur beziehen (mit direkten Profan-Prozeduren geht das nicht, sodass Profan selbst nicht multithreading-fähig ist)
s1$=@str$(%HWnd)+"²~³"+@str$(@addr(ThreadRunning1&))+"²~³"+@str$(hText1&)+"²~³1"'Übergabestring für Thread 1 mit dem Hauptfensterhandle, dem Flag ThreadRunning, dem Handle des Text-Dialogelements und dem Startwert füttern
hThread1&=~createthread(0,0,CProcAddr&,@addr(s1$),0,0)'Thread 1 starten
s2$=@str$(%HWnd)+"²~³"+@str$(@addr(ThreadRunning2&))+"²~³"+@str$(hText2&)+"²~³-10000"'Übergabestring für Thread 2 mit dem Hauptfensterhandle, dem Flag ThreadRunning, dem Handle des Text-Dialogelements und dem Startwert füttern
hThread2&=~createthread(0,0,CProcAddr&,@addr(s2$),0,0)'Thread 2 starten
s3$=@str$(%HWnd)+"²~³"+@str$(@addr(ThreadRunning3&))+"²~³"+@str$(hText3&)+"²~³12345678"'Übergabestring für Thread 3 mit dem Hauptfensterhandle, dem Flag ThreadRunning, dem Handle des Text-Dialogelements und dem Startwert füttern
hThread3&=~createthread(0,0,CProcAddr&,@addr(s3$),0,0)'Thread 3 starten
'Auf das Ende der Threads warten und reagieren
while (@gettext$(hText1&)<>"Fertig.") or (@gettext$(hText2&)<>"Fertig.") or (@gettext$(hText3&)<>"Fertig.")
waitinput
if %umessage=$1000'ein Thread signalisiert, dass er sich jetzt beendet und übergibt dabei sein Texthandle in &UwParam
uwp&=&UwParam
if uwp&=hText1&
settext hText1&,"Fertig."
elseif uwp&=hText2&
settext hText2&,"Fertig."
elseif uwp&=hText3&
settext hText3&,"Fertig."
endif
endif
endwhile
waitinput'damit man das Ende mit 3x "Fertig." sieht
'aufräumen
~closehandle(hThread1&)
~closehandle(hThread2&)
~closehandle(hThread3&)
usermessages 0
freedll hDLL%
end
Hinweise: · Ein auf diese Weise erzeugter Thread benutzt den Adressraum des aufrufenden Programms, weswegen problemlos Handles ausgetauscht werden können. Dabei läuft das Hauptprogramm weiter, während der Thread im "Hintergrund" (=asynchron) arbeitet. Ich benutze z.B. einen solchen Thread, um in einem Bildanzeigeprogramm das jeweils nächste Bild vorzuladen und dessen Handle an das aufrufende Programm zurückzuübermitteln (mittels der im Beispiel benutzten sendmessage-Methode).
· Einem neu zu startenden Thread kann nur exakt ein Parameter übergeben werden. Es bietet sich daher an, wie in dem Beispiel hierzu einen String zu benutzen, in dem alle notwendigen Informationen mit einem möglichst unzweideutigen Separator getrennt (hier "²~³") enthalten sind.
· Es gibt eine Windows-API-Funktion "GetExitCodeThread", die u.a. ermitteln soll, ob ein Thread noch aktiv ist. Diese Funktion funktioniert bis heute (Windows 10) nicht zuverlässig, sondern liefert dann und wann unvorhersehbar ein Ende des Threads zurück, obwohl dieser noch läuft. Daher ist in dem obigen Beispiel schon der Workaround eingebaut, der aber in dem Beispiel nicht benutzt wird: Der Thread sollte zu seinem Beginn ein Flag, dessen Adresse ihm übergeben wurde, auf 1 setzen, das dem aufrufenden Programm dessen Ausführung signalisiert, und direkt vor dem Thread-Ende sollte dieses Flag auf 0 gesetzt werden.
· whileloop sollte in einem Thread nicht benutzt werden. Dies könnte mit weiteren whileloops im aufrufenden Programm oder simultan laufenden anderen Threads kollidieren. while mit Zählervariable ist unter C ohnehin genauso schnell.
· Wenn man im Thread externe Funktionen aufruft, die auch im aufrufenden Programm (oder in weiteren Threads) gleichzeitig benutzt werden könnten, muss man diese Kollision mit einem gemeinsam genutzten Flag abfangen; es muss dann im jeweiligen Thread/im aufrufenden Programm so lange gewartet werden, bis die Funktion wieder frei ist. Sonst kann es zu einem Absturz ohne Fehlermeldung kommen.
Das Prinzip der gemeinsamen Nutzung von Flags sollte durch den Parameter-Übergabestring im Beispiel klar geworden sein. Immer daran denken: Es wird, anders als beim Multiprozessing, ein gemeinsamer Adressraum aller Threads und des aufrufenden Programmes verwendet, was gewünscht ist, aber auch zu Kollisionen führen kann.
· Im aufrufenden Programm bereits initialisierte DLLs (usedll) dürfen in der Thread-DLL nicht noch einmal initialisiert werden (sonst Programmabsturz). Die Einbindung der entsprechenden INC-Datei reicht.
· Alle mit ~createthread erstellten Threads müssen nach deren Beendigung mit ~closehandle wieder entfernt werden.
Viel Spaß beim Ausprobieren, und Anregungen sind natürlich jederzeit willkommen! |
| 232 kB | | Bezeichnung: | BeispielC.dll | | Version: | 1.0 | | Kurzbeschreibung: | Fertige DLL für das Multithreading-Beispiel | | Hochgeladen: | 20.06.2018 | | Ladeanzahl: | | | | Herunterladen |
| | | XProfan X4 * Prf2Cpp * XPSE * JRPC3 * Win11 Pro 64bit * PC i7-7700K@4,2GHz, 32 GB RAM PM: jreumsc@web.de | 19.06.2018 ▲ |
| |
| | p.specht
| | | | XProfan 11Computer: Gerät, daß es in Mikrosekunden erlaubt, 50.000 Fehler zu machen, zB 'daß' statt 'das'... | 20.06.2018 ▲ |
| |
| | | Coole Idee!
Treffenderer Titel wäre vielleicht "Multithreading per Profan2Cpp", da Multithreading mit XProfan entweder ab X4 per Inline-ASM-Funktionsadressen oder per xpse mit nProcs ( Threadbeispiele [...] [...] ) durchaus sonst relativ einfach anzuwenden ist.
cls
// 3 threads per myThread-Proc
createThread(,,procAddr(myThread,1),,,)
createThread(,,procAddr(myThread,1),,,)
createThread(,,procAddr(myThread,1),,,)
waitinput
nproc myThread
parameters data&
//...
return data&
endproc
|
| | | | |
| | | [offtopic] Habe das Thema in die Bibliothek nach SDK-Helfer und Tools verschoben. [/offtopic] |
| | | | |
| | Jens-Arne Reumschüssel | Moin,
ja klar, das geht natürlich schon lange mit nProcs, und das ist selbstverständlich absolut der Erwähnung wert. Allerdings ist man mit "normalen" XProfan-Procs flexibler. In meinem erwähnten Grafikanzeigeprogramm funktionieren nProcs leider nicht, weil XPSE da irgendwo aussteigt (nur, wenn man nProcs verwenden möchte, nicht generell). Wo, weiß ich nicht, weil ich noch keine Lust hatte, 16.000 Zeilen nach und nach auszukommentieren, um die Stelle zu finden, wo es hakt, aber das macht nichts. Grundsätzlich geht es ja. Das mit den Inline-ASM-Funktionsadressen ist mir übrigens nicht klar. Kann man damit normale Prozeduren zugänglich machen, oder müssen die in Assembler geschrieben sein?
Beste Grüße, Jens-Arne |
| | | XProfan X3XProfan X4 * Prf2Cpp * XPSE * JRPC3 * Win11 Pro 64bit * PC i7-7700K@4,2GHz, 32 GB RAM PM: jreumsc@web.de | 23.06.2018 ▲ |
| |
| | | Momentan müssten sie noch in Assembler geschrieben worden sein.
Deshalb würde mich ein XPSE-Update reizen, dass das "nProfan" in XProfan-Inline-Asm umsetzt. Ich könnte da vermutlich auch viel "Overhead" einsparen und neue Ordnung in XPSE bringen. Aber der Reihe nach... |
| | | | |
|
AntwortenThemenoptionen | 6.124 Betrachtungen |
ThemeninformationenDieses Thema hat 3 Teilnehmer: |