Thread schneller ausführen oder Alternative gesucht



  • Hallo Leute ,

    in meiner Anwendung mit virtuellem Listview habe ich eine Funktion die den Status „Selection“ (LVNI_SELECTED) für alle Items im Listview ermittelt. Dies dauert bei 999.999 von 1.000.000 selektierten Items recht lange. Generell ist mein Programm so aufgebaut, dass die eigentliche Arbeit per Thread aufgerufen wird und im Hintergrund abläuft. Grund hierfür ist die Bedienbarkeit des Programms, denn es ist ja unschön, wenn das Programmfenster während des Programmablaufes einfriert. Folgendes habe ich durch Zeitmessung im Programm herausgefunden.

    Ausführung der Funktion ohne Thread, Anwendung blockiert
    User: 1,267 Sekunden
    Admin: 0,617 Sekunden

    Ausführung der Funktion als Standard Thread
    User: 13,992 Sekunden
    Admin: 7,504 Sekunden

    Ausführung der Funktion als optimierter Thread
    User: 13,906 Sekunden
    Admin: 4,460 Sekunden

    Wie man sieht, wird die Funktion ohne eigenen Thread um ein vielfaches schneller ausgeführt. Das würde ich gerne, kombiniert mit der Bedienbarkeit der Anwendung, auch irgendwie hinbekommen. Weiterhin fällt auf, dass das Programm ebenfalls schneller arbeitet, wenn es mit Admin Rechten gestartet wurde. Wer kann helfen?

    /*
    ====================================================
    MyListview_GetAllSelection
    ====================================================
    */
    boolean MyListview_GetAllSelection(HWND hListview, lv_t *plv){
    
    	int				i, found = (-1), item_count, selection_count;
    
    	item_count = ListView_GetItemCount(hListview);
    	if(item_count != plv->anzahl_items){						//das darf nicht sein, da liegt ein Fehler vor
    		return FALSE;
    	}
    	selection_count = ListView_GetSelectedCount(hListview);
    	if(selection_count == item_count){						//wenn alle Items ausgewählt sind, muss "ListView_GetNextItem(...)" nicht aufrufen werden
    		for(i = 0; i < plv->anzahl_items; i++){
    			plv->item[i]->ent->tmp.is_selected = TRUE;
    		}
    	}else{
    		for(i = 0; i < plv->anzahl_items; i++){					//alle Items in der Struktur zurücksetzen
    			plv->item[i]->ent->tmp.is_selected = FALSE;
    		}
    		while((found = ListView_GetNextItem(hListview, found, LVNI_SELECTED)) != (-1)){	//die aktuellen Selections der Items ermitteln
    			plv->item[found]->ent->tmp.is_selected = TRUE;
    		}
    	}
    	return TRUE;
    }
    
    /*
    ================================================================================
    MyListview_ZeitMessung
    ================================================================================
    */
    void MyListview_ZeitMessung(void){
    
    	char				TimeString[128];
    	u_long				time_start, time_stop;
    
    	MyListview_SetWorkingState(WORKINGSTATE_WORKING);			//Menü, Toolbar und andere Controlls blockieren
    #ifdef	USE_TREAD_OPTIMIZE
    	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL));
    #endif
    //Start der Zeitmessung -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    	MY_LOG("Selection-State ermitteln...		"); 
    	timeBeginPeriod(1);
    	time_start = timeGetTime();
    
    	if(MyListview_GetAllSelection(prg.wndTab3LV1,&lv) == FALSE){
    		MY_ERRORMSG("MyListview_GetAllSelection: das war wohl nix!");
    	}
    
    //Ende der Zeitmessung-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    	time_stop = timeGetTime();
    	timeEndPeriod(1);
    	MY_LOG(va("Done!	%ss\r\n", Time_MillisecondsToStringSXXX((time_stop - time_start), TimeString)));
    	MyListview_SetWorkingState(WORKINGSTATE_NOTWORKING);		//Menü, Toolbar und andere Controlls wieder zur Bedienung freigeben
    
    #ifdef	USE_TREAD
    	_endthreadex(0);
    #endif
    }
    
    /*
    ====================================================
    MyListview_ZeitMessungStartThread
    ====================================================
    */
    void MyListview_ZeitMessungStartThread(void){
    
    	HANDLE			hThread;
    	DWORD				ThreadID = 0;
    
    #ifdef	USE_TREAD_OPTIMZE
    	SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS));
    #endif
    	hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&MyListview_ZeitMessung, NULL, 0, &ThreadID);
    	if(hThread){
    		CloseHandle((HANDLE)hThread);
    	}
    }
    


  • Deine ListView "lebt" in deinem "Hauptthread", und die Nachrichten (die hinter den Macros versteckt sind), werden auch von dem Hauptthread abgehandelt.
    Das macht in der Form absolut keinen Sinn, GUI Code in andere Threads zu verlagern.

    An sich müsste das alles aber schon schneller gehen. Es ist 20 Jahre her, dass ich direkt mit der Windows ListView was zu tun hatte, daher nur ein kurzer Vorschlag, du solltest dich intensiver damit auseinandersetzen.
    Oder noch besser - lass es. Ich weiß nicht, warum du das machst, aber obligatorischer Hinweis - kein Mensch programmiert mehr GUIs manuell mit der WinApi. Du wirst in extrem viele Probleme reinlaufen, die andere Frameworks schon lang gelöst haben.



  • @Mechanics sagte in Thread schneller ausführen oder Alternative gesucht:

    Deine ListView "lebt" in deinem "Hauptthread", und die Nachrichten (die hinter den Macros versteckt sind), werden auch von dem Hauptthread abgehandelt.
    Das macht in der Form absolut keinen Sinn, GUI Code in andere Threads zu verlagern.

    Du hast schon recht, wenn du sagt, dass die Macros im Code von Hauptthread bearbeitet werden, aber meiner Meinung nach habe die keinen negativen Einfluss. Dies kann ich so begründen, dass wenn ich den Aufruf "MyListview_GetAllSelection(..)" weglassen, dann müsste ein etwaiger negativer Einfluss immer noch da sein, aber nein die Zeitmessung ergibt dann 0,000 Sekunden für die Ausführung des Threads. Unabhänig jetzt vom Listview, denke ich dies ein Problem wie Windows mit einem weiteren Thread umgeht.

    @Mechanics sagte in Thread schneller ausführen oder Alternative gesucht:

    kein Mensch programmiert mehr GUIs manuell mit der WinApi. Du wirst in extrem viele Probleme reinlaufen, die andere Frameworks schon lang gelöst haben.

    Ich mach das als Hobby. Mir macht es Spass Winapi zu programmieren, beruflich mach ich was ganz anderes, daher sehe ich vorerst keine Veranlassung mich mit irgendwelchen Frameworks zu beschäftigen, die wahrscheinlich zumeist in C++ geschrieben sind und ich eh nur C kann. Trotzdem danke für die Antwort.



  • Ich kann dir leider auch nichts anderes als @Mechanics empfehlen 😉
    WINAPI ist in der heutigen Zeit ne Art Selbstgeißelung, da es ein dutzend Frameworks gibt, die wesentlich angenehmer zu handhaben sind.



  • @Oxigen sagte in Thread schneller ausführen oder Alternative gesucht:

    Du hast schon recht, wenn du sagt, dass die Macros im Code von Hauptthread bearbeitet werden, aber meiner Meinung nach habe die keinen negativen Einfluss.

    Das dürfte über die Message Queue von dem anderen Thread laufen. Das Dispatching dürfte durchaus einiges an Overhead erzeugen.
    Ich müsste mich selber wieder intensiver damit beschäftigen, damit ich da nichts falsches erzähle, ist schon alles zu lang her. Deswegen die etwas schwammigen Antworten. Aber wie gesagt, ich erwarte davon einiges an Overhead.



  • @Mechanics sagte in Thread schneller ausführen oder Alternative gesucht:

    Das dürfte über die Message Queue von dem anderen Thread laufen.

    Ja ist so.
    Ich habe in Zeile 73 mal Code eingefügt um die Zeitmessung zu starten, dann die Zeilen 49-51 ausgeschaltet und wie gehabt die Zeitmessung in Zeile 54 beendet und in Zeile 56 ausgegeben. Das ganze hat 21 Millisekunden gedauert um den Thread auszuführen. Aber das rechtfertigt nicht die von mir vorher gemessenen Werte. Die Funktion "MyListview_ZeitMessung" wird als Theread ganz einfach 10x (1,2Sek zu 13,9 Sek) langsammer ausgeführt und das hätte ich nach Möglichkeit gerne beschleunigt.
    Ich denke ich werde testweise mal eine andere Funktion wie z.B. qsort mit 1.000.000 Elementen im Thread laufen lassen und schauen wie sich das dann so verhält.



  • @Oxigen
    Das Problem ist dass MyListview_GetAllSelection nicht atomar ist. Das wird nicht als eine Message verschickt, sondern eine pro Item (plus 2-3 andere mehr aber die fallen nicht ins Gewicht).
    D.h. du zahlst bei 1 Mio. Einträge 1 Mio. mal die Kosten für den Umweg über die Message-Queue.

    Fix: Definiere eine eigene Message der du den Start-Index mitgibst sowie einen Zeiger auf ein Array wo sie die Resultate reinschreiben darf und die grösse dieses Arrays. Und falls du mehr als eine solche ListView hast natürlich das ListView Handle. Der Message Handler schreibt dann die angegebene Anzahl an ausgewählten Elementen (Indexen) in das Array und gibt zurück wie viele er geschrieben hat.

    Dann schickst du in MyListview_GetAllSelection diese Message wiederholt an dein Fenster um z.B. je 1000 Elemente zu holen. 1000 Elemente prüfen dürfte die GUI kaum merklich ausbremsen, aber du hast dann 1000 mal weniger Aufrufe durch die Message-Queue.

    Und schwupp-di-wupp läuft das auf einmal viel schneller.

    Was Admin vs. normalen User angeht: Windows hat einige Security-Checks eingebaut, auch bei den GUI Funktionen. Genauer kann ichs dir nicht sagen, aber ich denke das könnte der Grund für den Unterschied sein.



  • Ich würde zunächst mal prüfen, ob die Anzahl vielleicht nicht begrenzt werden kann. Millionen von Items gleichzeitig anzuzeigen und den Anwender dort eine sinnvolle Auswahl treffen zu lassen, halte ich nicht unbedingt für benutzerfreundlich.
    Ob nun mit Tabs oder anders kann hier natürlich keiner sagen. Da du die Daten anscheinend selber vorhältst, würde sich evtl. eine LV mit OwnerData und OwnerDraw anbieten.



  • @yahendrik sagte in Thread schneller ausführen oder Alternative gesucht:

    Ich würde zunächst mal prüfen, ob die Anzahl vielleicht nicht begrenzt werden kann. Millionen von Items gleichzeitig anzuzeigen und den Anwender dort eine sinnvolle Auswahl treffen zu lassen, halte ich nicht unbedingt für benutzerfreundlich.

    Gebe ich dir recht. Mein Programm ist erstmal nicht für den produktiven Gebrauch gedacht, sondern lediglich eine Testanwendung die mir hilft die Funktionsweise und Bedienbarkeit eines OwnerDraw Listview besser zu verstehen. Ich spiele hier mal so alle Varianten durch die man mit Items in einem Report-Listview so anstellen kann, z.B. einfügen, löschen, verschieben, sortieren, markieren, drag-and-drop, etc.. und das Ganze eben mit einer sehr großen Anzahl an Items um zu sehen wie sich das Programm zeitlich verhält.

    @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    Fix: Definiere eine eigene Message der du den Start-Index mitgibst sowie einen Zeiger auf ein Array wo sie die Resultate reinschreiben darf und die grösse dieses Arrays. Und falls du mehr als eine solche ListView hast natürlich das ListView Handle. Der Message Handler schreibt dann die angegebene Anzahl an ausgewählten Elementen (Indexen) in das Array und gibt zurück wie viele er geschrieben hat.

    hustbaer, Danke! Wie so oft kommt von dir ein super Lösungsvorschlag. Ich hab einen Thread gestartet der mir die Controls blockiert, die erstmal nicht bedienbar sein dürfen. Dann am Ende des Threads per PostMessage(...) die Anforderung an dem Thread im Hauptfenster schickt. Dort wird die eigendliche Arbeits-Funktion gestartet, am Ende der Arbeits-Funktion (je 10.000 Items werden verarbeitet) wird geprüft ob noch unbehandelte Items übrig sind, wenn ja wieder ein PostMessage(...) zum Aufruf der Arbeits-Funktion, wenn nein, Fertig. Ja was soll ich sagen die Ausführung als User dauert nun nur noch 1,330 Sekunden, also nur unwesentlich länger als die vorher blockierende Funktion. Da ich wahrscheinlich nie mit 1.000.000 Items arbeiten werde, ist die jetzige Lösung für mich perfekt.



  • @yahendrik sagte in Thread schneller ausführen oder Alternative gesucht:

    Millionen von Items gleichzeitig anzuzeigen und den Anwender dort eine sinnvolle Auswahl treffen zu lassen, halte ich nicht unbedingt für benutzerfreundlich.

    Ich finde das nicht unbedingt verkehrt. So lang man sehr schnell/effizient filtern und sortieren kann, finde ich das in Ordnung. Da würde ich z.B. Paging eher verwirrend finden. Was bringt es mir, wenn ich eine Leiste mit 1-1000 Seiten sehe? Ich muss doch dann genauso filtern oder zum ersten Buchstaben springen können usw. Das geht mit einer Ansicht mit 1 Mio Einträgen genauso, aber ohne das sinnlose Paging.
    Wenn das aber ein technisches Problem ist (z.B. tatsächlich 1 Mio Einträge aus der Datenbank holen und verarbeiten, die man sonst nicht brauchen würde), ist das natürlich was anderes.



  • @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    Was Admin vs. normalen User angeht: Windows hat einige Security-Checks eingebaut, auch bei den GUI Funktionen.

    Ja, ein User Programm darf auf jeden Fall keine Nachrichten an ein Programm schicken, das mit Admin Rechten läuft. Vielleicht nicht mal an ein Fenster eines anderen Benutzers, das weiß ich nicht.
    Aber es ist schon interessant, wieviel das auszumachen scheint. Und wie sie das dann überhaupt implementiert haben...



  • @Oxigen sagte in Thread schneller ausführen oder Alternative gesucht:

    @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    Fix: Definiere eine eigene Message der du den Start-Index mitgibst sowie einen Zeiger auf ein Array wo sie die Resultate reinschreiben darf und die grösse dieses Arrays. Und falls du mehr als eine solche ListView hast natürlich das ListView Handle. Der Message Handler schreibt dann die angegebene Anzahl an ausgewählten Elementen (Indexen) in das Array und gibt zurück wie viele er geschrieben hat.

    hustbaer, Danke! Wie so oft kommt von dir ein super Lösungsvorschlag. Ich hab einen Thread gestartet der mir die Controls blockiert, die erstmal nicht bedienbar sein dürfen. Dann am Ende des Threads per PostMessage(...) die Anforderung an dem Thread im Hauptfenster schickt. Dort wird die eigendliche Arbeits-Funktion gestartet, am Ende der Arbeits-Funktion (je 10.000 Items werden verarbeitet) wird geprüft ob noch unbehandelte Items übrig sind, wenn ja wieder ein PostMessage(...) zum Aufruf der Arbeits-Funktion, wenn nein, Fertig. Ja was soll ich sagen die Ausführung als User dauert nun nur noch 1,330 Sekunden, also nur unwesentlich länger als die vorher blockierende Funktion. Da ich wahrscheinlich nie mit 1.000.000 Items arbeiten werde, ist die jetzige Lösung für mich perfekt.

    Bitte 🙂
    Ich hab zwar nicht den Eindruck dass ich in letzter Zeit besonders viel super Lösungsvorschläge poste, aber wenn du das so wahrnimmst freut es micht.

    Klingt gut. Nur ... dann brauchst du ja eigentlich überhaupt keinen extra Thread mehr, oder? Also du könntest den ersten Teil (Controls blocken und erste Message posten) dann ja auch gleich im GUI Thread machen. Nicht?



  • @Mechanics Zwischen User und anderem User ist glaub ich auch nicht erlaubt. Was mich ein wenig wundert ist dass es auch einen Unterschied macht wenn alles mit dem selben User nur im GUI Thread läuft. Ich hätte mir erwartet dass es da einen fast-path gibt so dass der Check für genau diesen Fall sehr schnell mit "alles OK" beendet werden kann. Aber vielleicht ist das doch nicht so einfach machbar wie ich es mir vorstelle 🙂



  • @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    Ich hab zwar nicht den Eindruck dass ich in letzter Zeit besonders viel super Lösungsvorschläge poste, aber wenn du das so wahrnimmst freut es micht.

    Habe früher als unregistrierter User hier einiges angefragt, da hattes du mir oft schon geholfen. Daher lese ich immer genau durch was du zum Thema weist.

    @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    Klingt gut. Nur ... dann brauchst du ja eigentlich überhaupt keinen extra Thread mehr, oder? Also du könntest den ersten Teil (Controls blocken und erste Message posten) dann ja auch gleich im GUI Thread machen. Nicht?

    Liegt vielleicht daran wie ich die Messages der Controlls blockiere. Damit "Controll-funktioniert-gerade-nicht" für den User auch sichtbar ist, deaktiviere ich das Controll per "EnableWindow(..., FALSE)"als allererstes in meiner Arbeitsfunktion, am Ende wenn die Arbeit getan ist wird wieder eingeschaltet " "EnableWindow(..., TRUE)". Wenn ich das nicht über einen separaten Thread mache, dann sind die Controlls während der Arbeitsfunktiion zwar nicht bedienbar, aber leider werden sie auch nicht ausgegraut, sondern augenscheinlich aktiv dargestellt. K.A. vielleich mach ich da was falsch?



  • @Oxigen sagte in Thread schneller ausführen oder Alternative gesucht:

    Wenn ich das nicht über einen separaten Thread mache, dann sind die Controlls während der Arbeitsfunktiion zwar nicht bedienbar, aber leider werden sie auch nicht ausgegraut, sondern augenscheinlich aktiv dargestellt. K.A. vielleich mach ich da was falsch?

    Naja die erste Arbeitsfunktion blockiert dann ja nur die Controls und postet die Message und dann geht's weiter. Das ist schnell erledigt. Und dann sollte das mit dem Ausgrauen auch funktionieren.



  • @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    Naja die erste Arbeitsfunktion blockiert dann ja nur die Controls und postet die Message und dann geht's weiter. Das ist schnell erledigt. Und dann sollte das mit dem Ausgrauen auch funktionieren.

    Ja das ist wohl war, ist mir auch schon in den Sinn gekommen. Ich hab mal drüber nachgedacht, wie oft ich schon in meinen Programmen die Controls so deaktiviert habe. Oh Mann, da könnten vieleicht einige Sachen flotter laufen. Naja in Zukunft werde ich das dann so machen. Mal schauen habe bestimmmt 2 -3 Programme, die ich auch nacharbeiten sollte.



  • BTW: Was ich nicht weiss: wann WM_PAINT Nachrichten genau abgearbeitet werden. Was ich mich erinnere haben die relativ niedrige Priorität, d.h. es könnte sein dass die erst drankommen wenn keine selbst geposteten Nachrichten mehr da sind.
    (Also genaugenommen sind WM_PAINT glaube ich gar nie in der Queue sondern werden erzeugt wenn nix "wichtigeres" mehr ansteht und die Dirty-Region nicht leer ist. Aber was als "wichtiger" gilt weiss ich nimmer.)

    Anyway, falls du solche Probleme hast und die GUI trotz Aufteilung auf mehrere gepostete Messages immer noch einfriert: um sowas kann man mit Timern rumarbeiten. Einfach einen Timer mit Timeout 0 starten. Der wird dann erst bearbeitet wenn es sonst überhaupt nix mehr zu tun gibt, und dort kann man dann seine "Background-Tasks" machen. Und wenn man fertig ist, den Timer einfach wieder löschen - weil er ja sonst dauern feuern und die CPU auf 100% bringen würde.



  • @hustbaer sagte in Thread schneller ausführen oder Alternative gesucht:

    WM_PAINT

    OK, hab nachgesehen:

    The system sends this message when there are no other messages in the application's message queue.

    Also... wird vermutlich mit dem Messages posten trotzdem einfrieren. Also das Fenster rumschieben sollte schon gehen, klicken vermutlich auch, aber es könnte sein dass die GUI nicht mehr refresht bis die PostMessage-Orgie fertig ist. Aber möglicherweise reicht das auch so für dich.



  • Das müsste auch mittels InvalidateRect/UpdateWindow bzw. RedrawWindow (mit passenden Parametern) gehen (s.a die Antworten in Difference between InvalidateRect and RedrawWindow).


Log in to reply