Thread sauber beenden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

Hallo,

ich bin schon den ganzen Tag am suchen. Ich habe ein kleines Programm (Turtlegraphic) welches drei Schildkröten durch ein Fenster rennen lässt.
Die drei bewegen sich auch gleichzeitig alles so weit gut.

Aber das beenden der Threads will mir nicht gelingen. Wenn ich mit der Maus in das Fenster klicke dann beendet sich das Programm mit einer Fehlermeldung.

Bild

Hier das Skript

Code: Alles auswählen

#!/usr/bin/env python
#coding: utf8 

from threading import Thread
from turtle import *
import turtle
import random


class Turtanimator(Thread):
    def __init__(self, turtle):
        Thread.__init__(self)
        self.t = turtle

    def run(self):
        self.t.shape("turtle")
        while True:
            self.t.forward(150 * random.random())
            self.t.left(-180 + 360 * random.random())


# Schildkröte a definieren
a = Turtle()
a.up()
a.setpos(0,0)
a.color("red")
a.down()
a.speed(10)

# Schildkröte b definieren 
b = Turtle()
b.up()
b.setpos(0,0)
b.color("blue")
b.down()
b.speed(10)

# Schildkröte c definieren 
c = Turtle()
c.up()
c.setpos(0,0)
c.color("green")
c.down()
c.speed(10)

thread1 = Turtanimator(a)
thread2 = Turtanimator(b)
thread3 = Turtanimator(c)

thread1.start() 
thread2.start()
thread3.start()

turtle.exitonclick()

Fehlermeldung
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "./thread3.py", line 19, in run
self.t.left(-180 + 360 * random.random())
File "/usr/lib/python2.7/lib-tk/turtle.py", line 1614, in left
self._rotate(angle)
File "/usr/lib/python2.7/lib-tk/turtle.py", line 3110, in _rotate
self._update()
File "/usr/lib/python2.7/lib-tk/turtle.py", line 2565, in _update
self._drawturtle()
File "/usr/lib/python2.7/lib-tk/turtle.py", line 2825, in _drawturtle
width=w, top=True)
File "/usr/lib/python2.7/lib-tk/turtle.py", line 540, in _drawpoly
self.cv.itemconfigure(polyitem, fill=fill)
File "<string>", line 1, in itemconfigure
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 2412, in itemconfigure
return self._configure(('itemconfigure', tagOrId), cnf, kw)
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1325, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
TclError: invalid command name ".140180658307944"

Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "./thread3.py", line 19, in run
self.t.left(-180 + 360 * random.random())
File "/usr/lib/python2.7/lib-tk/turtle.py", line 1614, in left
self._rotate(angle)
File "/usr/lib/python2.7/lib-tk/turtle.py", line 3110, in _rotate
self._update()
File "/usr/lib/python2.7/lib-tk/turtle.py", line 2564, in _update
self._update_data()
File "/usr/lib/python2.7/lib-tk/turtle.py", line 2550, in _update_data
self.screen._incrementudc()
File "/usr/lib/python2.7/lib-tk/turtle.py", line 1239, in _incrementudc
raise Terminator
Terminator

Exception in thread Thread-3:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "./thread3.py", line 19, in run
self.t.left(-180 + 360 * random.random())
File "/usr/lib/python2.7/lib-tk/turtle.py", line 1614, in left
self._rotate(angle)
File "/usr/lib/python2.7/lib-tk/turtle.py", line 3108, in _rotate
self._update()
File "/usr/lib/python2.7/lib-tk/turtle.py", line 2567, in _update
screen._delay(screen._delayvalue) # TurtleScreenBase
File "/usr/lib/python2.7/lib-tk/turtle.py", line 590, in _delay
self.cv.after(delay)
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 591, in after
self.tk.call('after', ms)
RuntimeError: main thread is not in main loop
Zuletzt geändert von Anonymous am Freitag 24. Februar 2017, 15:03, insgesamt 2-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@DatMichi: Von Threads würde ich hier die Finger lassen. Threads und GUIs sind in der Regel nicht so einfach zu handhaben. `turtle` bietet die `ontimer()`-Funktion um etwas später ausführen zu lassen, wobei später auch 0 Millisekunden sein darf. Damit kann man die ``while``-Schleifen ersetzen, in dem man in einer Funktion oder Methode die den Schleifenkörper enthält immer wieder diese Funktion oder Methode am Ende wieder erneut für einen Aufruf registriert.
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

Danke erstmal für dein Antwort...

Das ganze dient eigentlich nur der Veranschaulichung und des verstehen des ganzen.
Die spätere Anwendung ist eine Terminal Anwendung auf dem Raspberry Pi. Da möchte ich mit einer
RGB Diode die Farben einzel und unabhängig zu mischen.

Aber dennoch muss es auch mit der GUI möglich sein einen Thread sauber zu beenden
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Natürlich ist das möglich, du musst jedem deiner Threads genügend Zeit geben um sich selbst zu beenden, dazu musst du dem Thread erst signalisieren, dass er sich beenden soll und dann lange genug warten.

Aber dein ganzes GUI setup mit Threads ist, wenn es funktioniert, im Grunde nur Zufall. Deshalb solltest du die Funktion die dir BlackJack vorgestellt hat benutzen. Andere Toolkits bieten eine "post_event" Methode um aus einem Thread heraus in der Mainloop Code auszuführen, eventuell hat die Turtle/tk sowas auch.
the more they change the more they stay the same
BlackJack

@DatMichi: Ja, in dem man das programmatisch vorsieht den Thread darüber zu informieren dass er sich beenden soll. Und den Thread nicht an GUI-Elementen herumfummeln lässt, was in der Regel nicht gut geht, weil GUI-Rahmenwerke normalerweise allgemein nicht threadsafe sind und die Einschränkung haben das man sie nur aus dem Thread heraus manipulieren darf in dem die GUI-Hauptschleife läuft.

Zwischen Turtle mit Sternchenimport und den ganzen impliziten globalen Zuständen und sauberer Programmierung liegen Welten. Bei der Beschreibung vom tatsächlichen Problem sehe ich auch nicht wo da ein Thread auf die GUI zugreift. Threading käme da ja eher zum Einsatz um das PWM für die LED zu realisieren, dass dann aber ziemlich wahrscheinlich nicht in Python. Das wäre dann in einer C-Bibliothek ausgelagert, und von da greift dann sicher nichts auf die GUI zu, denn von irgendeiner GUI weiss so eine Bibliothek nichts.
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

Es geht mir ja nicht das Turtle sauber läuft.... es geht mir darum das ich das arbeiten mit Threads lerne. Darum bringt es nicht viel wenn ich Turtle eigene Funktionen nutze.

DAS IST MEIN PROBLEM --> RuntimeError: main thread is not in main loop

Immer wenn ich im main eine while Schleife setze bekomme ich die Meldung...
BlackJack

@DatMichi: Das Arbeiten mit Threads lernt man am besten ohne GUI, denn bei GUIs ist die erste Frage in der Regel immer welche Möglichkeiten bietet das GUI-Rahmenwerk das Problem ohne Threads zu lösen. Selbst ohne GUI würde ich versuchen ein Problem erst einmal ohne Threads zu lösen. Und falls es doch Threads sein müssen, dann nicht direkt, sondern zum Beispiel über `concurrent.futures`.

Wenn Du wirklich den Umgang mit Threads lernen möchtest, dann such Dir erst einmal Beispiele bei denen Threads wirklich Sinn machen. Und dann welche bei denen die typischen Probleme auftreten. Da man das nicht selber kann wenn man Threads/nebenläufige Programmierung erst lernt, müsste man sich Lehrmaterialien dazu suchen.

Habe übrigens gerade festgestellt, dass auch die `ontimer()`-Variante das gleiche Problem aufweist, weil `exitonclick()` beziehungsweise das `bye()` was dahinter steckt, nur die GUI zerstört, aber nicht die Hauptschleife beendet. Wahrscheinlich mit Rücksicht auf IDLE.

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function
import random
from functools import partial
from turtle import *


def do_turtle_walk(turtle):
    turtle.forward(150 * random.random())
    turtle.left(-180 + 360 * random.random())
    ontimer(partial(do_turtle_walk, turtle), 100)


def main():
    for color in ['red', 'blue', 'green']:
        turtle = Turtle('turtle')
        turtle.up()
        turtle.setpos(0, 0)
        turtle.color(color)
        turtle.down()
        turtle.speed(10)
        do_turtle_walk(turtle)

    exitonclick()


if __name__ == '__main__':
    main()
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

Danke dir für dein Test...

Ich schaue mir concurrent.futures an vielleicht ist das ja was ich brauche. Wie gesagt ich muss nur drei Routinen parallel laufen lassen.
Es müssen keine Daten oder sonst etwas ausgetauscht werden und das auf der Konsole reicht. Da es später auf einem Raspberry laufen soll.
BlackJack

@DatMichi: Falls es nur um die Ausgabe über eine RGB-LED geht und Du vorhast da PWM in Software *in Python* zu machen, dann würde ich sagen: Vergiss es. Das ist an sich wahrscheinlich zu ungenau und würde flackern wie Sau, aber dann kommt da noch mal das „global interpreter lock“ (GIL) dazu das bei CPython effektiv verhindert das Python-Bytecode tatsächlich parallel läuft.
Benutzeravatar
noisefloor
User
Beiträge: 3853
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@DatMichi: Threads lassen sich OOTB unter Python nicht beenden. Das musst du wenn selber implementieren. Eine Suche nach "python cancel thread" oder "python stop thread" wird dir unzählige Seiten bei Stack Overflow liefern, wo genau das Problem gehandelt wird.

Das empfohlene `concurrent.futures` Modul arbeitet auf einer höheren Abstraktionsebene als das threading-Modul und bietet auch die Möglichkeit, einen über concurrent.futures gestarteten Thread sauber zu beenden.

Und bevor du das für den Raspi selber implementierst: das gpiozero-Modul hat eine fertige Klasse dafür (Link).

Gruß, noisefloor
BlackJack

@noisefloor: Auch mit `concurrent.futures` kann man Threads nicht einfach so auf magische Weise sauber beenden. Das `cancel()` auf dem `Future`-Objekt funktioniert nur wenn der Thread dafür noch gar nicht gestartet wurde.

POSIX-Threads lassen sich generell nicht ohne Kooperation sauber beenden. Das ist nicht auf Python beschränkt. POSIX kennt zwar eine Funktion um einen Thread von aussen zu töten, deren Dokumentation rät davon aber dringend ab das tatsächlich zu tun. Java hat eine `Thread.stop()`-Methode, die ist aber schon seit Ewigkeiten (mindestens seit Java 1.2) „deprecated“ und mit einer fetten Warnung versehen das schlimme Dinge passieren können wenn man die aufruft.
Benutzeravatar
noisefloor
User
Beiträge: 3853
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Das `cancel()` auf dem `Future`-Objekt funktioniert nur wenn der Thread dafür noch gar nicht gestartet wurde.
Stimmt - hatte ich wohl falsch im Kopf.

Bleibt aber trotzdem noch, dass `concurrent.futures` die schönere API hat :-)

Gruß, noisefloor
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

BlackJack hat geschrieben:@DatMichi: Falls es nur um die Ausgabe über eine RGB-LED geht und Du vorhast da PWM in Software *in Python* zu machen, dann würde ich sagen: Vergiss es. Das ist an sich wahrscheinlich zu ungenau und würde flackern wie Sau, aber dann kommt da noch mal das „global interpreter lock“ (GIL) dazu das bei CPython effektiv verhindert das Python-Bytecode tatsächlich parallel läuft.
Das mit dem PWM Signal um die Farben weich ein und auszublenden das Funktioniert extrem sauber. Habe es für alle drei Farben von 0 bis 100% nacheinander getestet.
BlackJack

@DatMichi: In PWM in *Software* in *Python*? Und falls ja, welche Bibliothek verwendest Du denn um die GPIOs anzusprechen?
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

BlackJack hat geschrieben:@DatMichi: In PWM in *Software* in *Python*? Und falls ja, welche Bibliothek verwendest Du denn um die GPIOs anzusprechen?
Guten morgen BlackJack,

hier mal mein kleines Python Skript. Dabei werden die Farben blau, rot, grün nacheinander eingeblendet.
Ich verwende diese LEDs

Angeschlossen habe ich die LED am Raspberry so

Bild

Code: Alles auswählen

#!/usr/bin/env python
#coding: utf8    
 
# RGB LED mit vier Pins Steuern 
#
# ACHTUNG !!!
# dieses Beispiel ist für LEDs mit gemeinsamer KATHODE
# LOW = aus  HIGH = an
#
# Dimmen einer LED mit PWM
 
import RPi.GPIO as GPIO
import time
 
r = 0
g = 0
b = 0
verz = 0.05
 
GPIO.setmode(GPIO.BOARD)  
GPIO.setwarnings(False)
 
# Initialisierung des Pins
LED_R = 16
LED_G = 18
LED_B = 22
 
GPIO.setup(LED_R, GPIO.OUT)
GPIO.setup(LED_G, GPIO.OUT)
GPIO.setup(LED_B, GPIO.OUT)
 
# Initialisierung einer PWM-Zugriffsvariablen 
RPWM_R = GPIO.PWM(LED_R, 100)
RPWM_G = GPIO.PWM(LED_G, 100)
RPWM_B = GPIO.PWM(LED_B, 100)
 
RPWM_R.start(r)
RPWM_G.start(g)
RPWM_B.start(b)
 
# Dimmen der LED
 
# Blau einblenden
for b in range(0, 100, 1):
  RPWM_B.start(b)
  time.sleep(verz)
 
# Blau ausblenden
for b in range(100, 0, -1):
  RPWM_B.start(b)
  time.sleep(verz)
 
 
# Rot einblenden
for r in range(0, 100, 1):
  RPWM_R.start(r)
  time.sleep(verz)
 
# Rot ausblenden
for r in range(100, 0, -1):
  RPWM_R.start(r)
  time.sleep(verz)
 
 
# Grün einblenden
for g in range(0, 100, 1):
  RPWM_G.start(g)
  time.sleep(verz)
 
# Grün ausblenden
for g in range(100, 0, -1):
  RPWM_G.start(g)
  time.sleep(verz)
Zuletzt geändert von Anonymous am Samstag 25. Februar 2017, 13:14, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Bei deinem zuletzt geposteten Code passiert aber nichts gleichzeitig, obwohl du zuvor davon gesprochen hast. Es hörte sich so an (vermutlich auch für BlackJack), dass die LEDs parallel zueinander von dir gesteuert würden.
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

Oh da muss ich mich entschuldigen... Das nächste mal versuche ich mein Problem etwas genau zu schildern.

Der zuletzt gepostete Code ist für BlackJack da er mich frage welche Bibliothek usw. ich verwende. Darum habe ich gleich mal alles veröffentlicht.
Das Python Skript soll aber so umgebaut werden das die EINE Diode angesteuert wird. So das es zu Mischfarben kommt und somit zu Farbverläufen kommt.
Benutzeravatar
noisefloor
User
Beiträge: 3853
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

die gpiozero Bibliothek (welche "moderner und empfehlenswerter ist als das ältere rpi.gpio) hat fertige Klassen und Methoden, um RGB-LEDs direkt anzusteuern, optional auch mit PWM und optional auch direkt als Hintergrundthread.

Gruß, noisefloor
DatMichi
User
Beiträge: 29
Registriert: Montag 25. Mai 2015, 20:35

noisefloor hat geschrieben:Hallo,

die gpiozero Bibliothek (welche "moderner und empfehlenswerter ist als das ältere rpi.gpio) hat fertige Klassen und Methoden, um RGB-LEDs direkt anzusteuern, optional auch mit PWM und optional auch direkt als Hintergrundthread.

Gruß, noisefloor

OH danke für deine Info dann werde ich mir diese auch mal anschauen.
BlackJack

@DatMichi: Da sehe ich aber immer noch nicht wo das Threads ins Spiel kommen sollen. Wenn man nicht `gpiozero` verwendet, würde man sich so eine RGBLED-Klasse wie dort halt einfach selbst schreiben. Wofür braucht man da Threads? (Die startet `GPIO` schon selbst im Hintergrund pro PWM-Objekt.)

Ich würde übrigens nicht immer wieder `start()` aufrufen wenn ich eigentlich `ChangeDutyCycle()` meine.
Antworten