backgroundworker



  • Morgen,

    Jockelx schrieb:

    So ein C#-Backgroundworker würde doch auch nicht den main-thread aus dem while(true) unterbrechen oder?

    Ich würde einfach eine shared-bool-Variable nehmen (std::atomic<bool>) und im while abfragen und im Thread setzen.

    aber das waere ja busy-wating was nicht so gut ist. Besser waere hier wohl eine conditional-variable. Damit kann sich der main-thread schlafen legen solange bis ein notify kommt.

    Gruessle



  • Der main-thread soll aber doch gar nicht schlafen (soweit ich das verstehe).



  • Jockelx schrieb:

    Der main-thread soll aber doch gar nicht schlafen (soweit ich das verstehe).

    Oh mist das habe ich uebersehen 😡 . Ja stimmt wenn das so ist einfach immer wieder mal eine Variable ueberpruefen im "tu was sinnvolles" Teil.
    Hmm aber ich glaub das ist auch nicht ganz das was er will, da er von Callbacks redet.

    @Happystudent: du solltest genauer erklaeren was du erreichen willst. So kann man dir nicht wirklich helfen



  • @Happystudent:
    Kannst dich ja mal durch den Code vom BackgroundWorker klicken.

    http://referencesource.microsoft.com/#System/compmod/system/componentmodel/BackgroundWorker.cs,85d60b0d93a826fa

    Ob es Sinn ergibt, den nachbilden zu wollen, glaub ich aber nicht.



  • stuxn schrieb:

    Oh mist das habe ich uebersehen 😡 . Ja stimmt wenn das so ist einfach immer wieder mal eine Variable ueberpruefen im "tu was sinnvolles" Teil.
    Hmm aber ich glaub das ist auch nicht ganz das was er will, da er von Callbacks redet.

    @Happystudent: du solltest genauer erklaeren was du erreichen willst. So kann man dir nicht wirklich helfen

    Ok, dann werd ich das mal genauer erklären.

    Also. Ich habe eine GUI, mit der der Benutzer interagieren kann. Unter anderem kann er eine (Laufzeit-intensive) Operation daraus starten. Da diese Operation lange dauern kann, will ich diese parallel ausführen um die GUI nicht zum ruckeln/einfrieren zu bringen.

    Gleichzeitig soll der Benutzer aber die Möglichkeit haben, die Operation neu zu starten. Wenn die vorherige Operation noch nicht fertig ist, soll diese abgebrochen werden und die neue gestartet werden. Vom Prinzip her sieht das so aus (stark vereinfacht):

    #include <thread>
    #include <atomic>
    #include <chrono>
    #include <iostream>
    
    std::atomic<bool> stop = false;
    
    void long_operation()
    {
    	for (int i = 0; i < 100; ++i)
    	{
    		if (stop)
    		{
    			stop = false;
    			return;
    		}
    		std::this_thread::sleep_for(std::chrono::milliseconds(10));
    	}
    	std::cout << "finished!\n"; // Sollte auf der GUI ausgegeben werden
    }
    
    void start_operation_by_user()
    {
    	stop = true;
    	std::this_thread::sleep_for(std::chrono::milliseconds(30)); // Suboptimal, warte bis vorheriger thread gestoppt wurde
    	stop = false;
    
    	std::thread t(long_operation);
    	t.detach();
    }
    
    int main()
    {
    	start_operation_by_user();
    
    	while (true)
    	{
    		int user_input;
    		std::cin >> user_input;
    		if (user_input)
    		{
    			start_operation_by_user();
    		}
    	}
    }
    

    Probleme bei dieser Methode: Um sicher zu gehen dass der noch laufende thread gestoppt wird muss ich eine geschätzte Zeit warten (hier 30 ms), was natürlich unzuverlässig und ineffizient ist.

    Zweites Problem ist, dass das Ergebnis auf der GUI ausgegeben werden soll (also nocht per cout wie in dem Beispiel), das Ganze sollte also im original thread passieren. Da der aber per detach von der Leine gelassen wurde, habe ich ja keine Möglichkeit mehr den zu überprüfen. Deswegen würde ich gerne in long_operation (wenn diese fertig ist) eine Funktion im aufrufenden thread callen.

    So, ich hoffe das macht das Ganze etwas verständlicher, über Lösungsvorschläge (zu beiden Problemen) würde ich mich sehr freuen 🙂



  • happystudent schrieb:

    Probleme bei dieser Methode: Um sicher zu gehen dass der noch laufende thread gestoppt wird muss ich eine geschätzte Zeit warten (hier 30 ms), was natürlich unzuverlässig und ineffizient ist.

    Wenn du den Thread nicht detachen würdest, könntest du ihn statt dessen einfach joinen. Dein sleep_for -Ansatz ist sowieso strikt falsch, da du mit sleep_for niemals garantieren kannst dass der Worker sich beendet hat.

    happystudent schrieb:

    Zweites Problem ist, dass das Ergebnis auf der GUI ausgegeben werden soll (also nocht per cout wie in dem Beispiel), das Ganze sollte also im original thread passieren. Da der aber per detach von der Leine gelassen wurde, habe ich ja keine Möglichkeit mehr den zu überprüfen. Deswegen würde ich gerne in long_operation (wenn diese fertig ist) eine Funktion im aufrufenden thread callen.

    Dann wirst du eine Message an den GUI Thread schicken müssen. Was auch die übliche Variante wäre wie man das löst.
    Der Worker benachrichtigt die GUI wenn er fertig geworked hat.
    Wie man Messages an den GUI Thresd schickt, so dass dieser sie "automatisch" über den Message-Loop aufgreift, ist abhängig von der GUI.
    Bei Win32 verwendet man z.B. einfach PostMessage .
    Falls es aus irgend einem Grund überhaupt keine Möglichkeit gäbe (was sehr seltsam wäre), müsstest du pollen.



  • hustbaer schrieb:

    Wenn du den Thread nicht detachen würdest, könntest du ihn statt dessen einfach joinen.

    Aber wie mache ich es dann, dass die GUI nicht einfriert? Wenn ich statt detach join nehme, wird in start_operation_by_user halt einfach gewartet bis der thread fertig ist (was zu ruckeln führt)... Oder gibt es eine andere Möglichkeit einen thread "weiterlaufen" zu lassen als detach?

    hustbaer schrieb:

    Dein sleep_for -Ansatz ist sowieso strikt falsch, da du mit sleep_for niemals garantieren kannst dass der Worker sich beendet hat.

    Ja, der Ansatz ist 👎
    Ich verwende das momentan als Hack, da es mit einer enstprechend großer Wartezeit halt praktisch funktioniert, würde aber gerne davon wegkommen.

    hustbaer schrieb:

    Dann wirst du eine Message an den GUI Thread schicken müssen. Was auch die übliche Variante wäre wie man das löst.
    Der Worker benachrichtigt die GUI wenn er fertig geworked hat.
    Wie man Messages an den GUI Thresd schickt, so dass dieser sie "automatisch" über den Message-Loop aufgreift, ist abhängig von der GUI.
    Bei Win32 verwendet man z.B. einfach PostMessage .
    Falls es aus irgend einem Grund überhaupt keine Möglichkeit gäbe (was sehr seltsam wäre), müsstest du pollen.

    Ok, werde das mal mit PostMessage ausprobieren, danke. Dann ist hoffentlich wenigstens das Problem gelöst 🙂



  • Du sollst ja nicht sofort joinen, sondern erst, sobald du auf das Ergebnis wartest.



  • Wenn du nicht detach verwendest dann musst du natürlich sicherstellen dass das std::thread Objekt so lange lebt wie der Thread läuft. => z.B. Membervariable

    Und was das Beenden angeht: auch hier kannst du dir vom Thread eine Message posten lassen wenn du unbedingt sicherstellen willst dass die GUI nicht einfriert während auf die Beendigung des Threads gewartet wird.

    Eine andere recht elegante Art das Problem zu lösen wäre dem Thread einfach nur zu signalisieren dass er sich beenden soll, und ihn dann zu detachen.
    Dabei darfst du dann natürlich nicht mehr aus dem Thread auf irgendwelche Melber der Fenster- bzw. Dialogklasse zugreifen.
    Genau so musst du zur Kommunikation mit dem Thread ein eigenes Objekt erzeugen wo z.B. die Mutex und die Conditiaon-Variable drinnen sind.



  • Jockelx schrieb:

    Du sollst ja nicht sofort joinen, sondern erst, sobald du auf das Ergebnis wartest.

    hustbaer schrieb:

    Wenn du nicht detach verwendest dann musst du natürlich sicherstellen dass das std::thread Objekt so lange lebt wie der Thread läuft. => z.B. Membervariable

    Und was das Beenden angeht: auch hier kannst du dir vom Thread eine Message posten lassen wenn du unbedingt sicherstellen willst dass die GUI nicht einfriert während auf die Beendigung des Threads gewartet wird.

    Omg, jetzt hats Klick gemacht!

    Manchmal denkt man viel zu kompliziert, so klappt das ja 🕶



  • Hallo nochmal,

    also ich habe jetzt folgenden Code (der auch sehr gut funktioniert), zu dem ich aber noch eine Frage hätte:

    #include <thread>
    #include <atomic>
    #include <chrono>
    #include <iostream>
    
    std::atomic<bool> stop = false;
    
    void long_operation()
    {
    	for (int i = 0; i < 100; ++i)
    	{
    		if (stop)
    		{
    			return;
    		}
    		std::this_thread::sleep_for(std::chrono::milliseconds(10));
    	}
    	std::cout << "finished!\n"; // Wird per PostMessage erledigt
    }
    
    std::thread t;
    
    void stop_background_thread()
    {
    	if (t.joinable())
    	{
    		stop = true;
    		t.join();
    		stop = false;
    	}
    }
    
    void start_operation_by_user()
    {
    	stop_background_thread();
    	t = std::thread(long_operation);
    }
    
    int main()
    {
    	while (true)
    	{
    		int user_input;
    		std::cin >> user_input;
    		if (user_input)
    		{
    			start_operation_by_user();
    		}
    	}
    }
    

    und zwar besteht long_operation aus sehr vielen Unterfunktions-Funktionsaufrufen. Wenn ich jetzt in jeder Unterfunktion checken muss ob stop gesetzt wurde, ist das sehr nervig. Vor allem weil ich diese information dann ja weiterreichen muss, je nachdem über 7 bis 8 Aufrufe hinweg.

    Daher meine Frage, ist es hier empfehlenswert Exceptions zu verwenden? Oder hat man (zum Beispiel) mit Performance-Einbusen zu rechnen? Ich hab da an etwas der Art gedacht:

    #include <thread>
    #include <atomic>
    #include <chrono>
    #include <iostream>
    #include <exception>
    
    std::atomic<bool> stop = false;
    
    struct worker_stop_exception : std::exception {};
    
    void check_stop_condition()
    {
    	if (stop)
    	{
    		throw worker_stop_exception();
    	}
    }
    
    void sub_operation()
    {
    	for (int i = 0; i < 100; ++i)
    	{
    		check_stop_condition(); // Testen, ob worker stoppen soll
    		std::this_thread::sleep_for(std::chrono::milliseconds(10));
    	}
    	std::cout << "finished!\n"; // Wird per PostMessage erledigt
    }
    
    void long_operation()
    {
    	try
    	{
    		sub_operation();
    	}
    	catch (worker_stop_exception)
    	{
    		// Mache nix, worker wurde gestoppt!
    	}
    }
    
    std::thread t;
    
    void stop_background_thread()
    {
    	if (t.joinable())
    	{
    		stop = true;
    		t.join();
    		stop = false;
    	}
    }
    
    void start_operation_by_user()
    {
    	stop_background_thread();
    	t = std::thread(long_operation);
    }
    
    int main()
    {
    	while (true)
    	{
    		int user_input;
    		std::cin >> user_input;
    		if (user_input)
    		{
    			start_operation_by_user();
    		}
    	}
    }
    

    Damit wäre die Überprüfung der stop-Condition nur ein One-Liner in jeder Unterfunktion, und um das korrekte zurückschleusen durch etliche Unterfunktions-Aufrufe müsste ich mich auch nicht mehr kümmern.

    Die exception würde hier halt ihrem Namen nicht wirklich gerecht, da es sich um keine wirklich "Ausnahme" mehr handeln würde, sondern quasi "Absicht" wäre. Auch der leere catch-Block sieht etwas nach Hack aus...

    Passt das so, oder gibt es Kritik/Probleme mit dieser Methode?


Anmelden zum Antworten