2016-05-13 8 views
2

Ich versuche, Aufgaben hinter einem GUI zu automatisieren. Meine GUI hat mehrere Tasten, die bestimmte Funktionen starten sollen, und eine Taste, um jede laufende Funktion zu stoppen. Wenn eine Funktion ausgeführt wird, sind alle Schaltflächen außer der Schaltfläche "Abbrechen" ausgegraut. Ich brauche den zusätzlichen Thread, damit meine GUI noch reagiert, während die langen Funktionen laufen.Dekorateur für zusätzlichen Thread

Jetzt möchte ich einen Dekorateur implementieren, um diese Funktionen zu dekorieren. Dieser Dekorator sollte die Funktion in einem separaten Thread ausführen. Wenn der Benutzer die Abbruchtaste drückt, sollte die Funktion in diesem zusätzlichen Thread eine Art Stoppsignal empfangen, das die eingerichtete Funktion verwenden kann, um ihre aktuelle Aufgabe zu beenden und zu beenden.

Ich weiß, dass es möglich ist, eine Klasse des Typs threading.Thread für jede Funktion zu implementieren und die aktuelle Funktion als "def run (self):" einzufügen, aber es scheint nicht wie eine elegante Lösung.

Gibt es einen Weg? Für mich scheint es ein häufiges Problem zu sein, aber ich habe keine Lösung über Google gefunden, außer um die Funktionen als Klassen zu schreiben und diese als separate Threads auszuführen.

Bearbeiten 1:

Lassen Sie mich ein paar Beispiele hinzufügen. Gerade jetzt sehen meine Funktionen wie folgt aus:

def function1: 
    function_code 

Aber wenn ich Klassen schaffen würde es so aussehen:

class class1(threading.Thread): 
    stopper = None 
    def __init__(self): 
     init_code 
    def run(self): 
     function_code 

def function1: 
    t = class1() 
    t.stopper = some_stop_condition 
    t.run() 

Der zweite Code ist viel länger und braucht eine Klasse und eine Funktion für jede Taste. Es erscheint einfach viel komplizierter und ich hoffe es ist unnötig.

Bin ich falsch oder mache ich etwas falsch?

Edit 2:

Mein neuer Kodex nach großartigem Beispiel des salomonderossi:

def run_async(func): 
    @functools.wraps(func) 
    def async_func(*args, **kwargs): 
     queue = Queue.Queue() 
     t = threading.Thread(target=func, args=(queue,) + args, kwargs=kwargs) 
     t.start() 
     return queue, t 

    return async_func 


# @layout_decorators.runInSeparateThread() 
@run_async 
def test2(myQueue): 
    print "executing test2" 
    import time 
    for k in range(6): 
     print k 
     try: 
      myQueue.get(False) 
     except Queue.Empty: 
      print "cancelled" 
      return 
     time.sleep(1) 


def test22(): 
    print "executing test22" 
    global globalQueue 
    globalQueue, t = test2() 


if __name__ == "__main__": 
    import time 
    print "\n\n" 
    test22() 
    time.sleep(2) 
    globalQueue.put("stop") 

Aber es stoppt den Faden an der ersten Chance. Auch wenn ich die letzte Zeile entfernen (was ich dachte, stoppt den Faden) meine Ausgabe

executing test22 
executing test2 
0 
cancelled 
+0

Warum ist die Klasse 'threading.Thread' keine elegante Lösung? – qvpham

+0

@AlexHall: Ich will es nicht töten, ich will die Funktion das Haltesignal handhaben und von selbst verlassen. – Stefan

+0

@julivico: Weil ich müsste 5 Klassen statt 5 Funktionen und einen Dekorateur schreiben. Diese Klassen wären bis auf die interne run-Funktion identisch. Ich weiß, dass ich von einem von ihnen erben könnte und die Lauf-Funktion für jeden von ihnen ändern könnte. Scheint immer noch zu kompliziert für diese scheinbar einfache Aufgabe. – Stefan

Antwort

3

Der Dekorateur muss, ist, einen Weg zu schaffen, mit dem Thread comminucate. In diesem Beispiel muss jede Funktion, die als Thread ausgeführt werden soll, das Argument queue an erster Stelle haben. Diese queue wird verwendet, um mit den Threads zu kommunizieren. In diesem Beispiel kann alles in die Warteschlange gestellt werden, um den Thread zu stoppen, da die Funktion nur überprüft, ob ein Wert aus der Warteschlange entfernt werden kann.

Mit queue.get(False) versucht die Funktion ein Element aus der Warteschlange zu bekommen (ohne zu warten). Wenn kein Element vorhanden ist (die Warteschlange ist leer), wird eine Exception ausgelöst. Andernfalls befindet sich etwas in der Warteschlange und der Thread wird aufgefordert, den Vorgang abzubrechen.

Um den Thread zu beenden, muss etwas in die Thread-Warteschlange gestellt werden. Dies geschieht mit queue.put("stop"). In diesem Fall spielt das Argument keine Rolle.

Das bedeutet, dass Sie überprüfen müssen, ob sich etwas in der Warteschlange befindet und darauf reagieren (In diesem Fall stoppen Sie einfach die Verarbeitung).

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

from threading import Thread 
from functools import wraps 
from time import sleep 

try: 
    import queue as Queue 
except ImportError: 
    import Queue as Queue 


def run_async(func): 
    @wraps(func) 
    def async_func(*args, **kwargs): 
     queue = Queue.Queue() 
     t = Thread(target=func, args=(queue,) + args, kwargs=kwargs) 
     t.start() 
     return queue, t 

    return async_func 

@run_async 
def do_something_else(queue): 
    while True: 
     sleep(1) 
     print("doing something else") 
     # check if something in the queue and return if so 
     try: 
      queue.get(False) 
     except Queue.Empty: 
      pass 
     else: 
      print("Told to quit") 
      return 


@run_async 
def print_somedata(queue): 
    print('starting print_somedata') 
    sleep(2) 
    try: 
     queue.get(False) 
    except Queue.Empty: 
     pass 
    else: 
     print("Told to quit") 
     return 

    print('print_somedata: 2 sec passed') 
    sleep(2) 
    try: 
     queue.get(False) 
    except Queue.Empty: 
     pass 
    else: 
     print("Told to quit") 
     return 

    print('print_somedata: another 2 sec passed') 
    sleep(2) 
    try: 
     queue.get(False) 
    except Queue.Empty: 
     pass 
    else: 
     print("Told to quit") 
     return 

    print('finished print_somedata') 


def test(): 
    threads = list() 

    # at this moment the thread is created and starts immediately 
    threads.append(print_somedata()) 
    print('back in main') 

    # at this moment the thread is created and starts immediately 
    threads.append(print_somedata()) 
    print('back in main') 

    # at this moment the hread is created and starts immediately 
    threads.append(do_something_else()) 
    print('back in main') 

    # at this moment the hread is created and starts immediately 
    threads.append(do_something_else()) 
    print('back in main') 

    print("Wait a bit in the main") 
    sleep(1) # uncomment the wait here to stop the threads very fast ;) 

    # you don't have to wait explicitly, as the threads are already 
    # running. This is just an example to show how to interact with 
    # the threads 
    for queue, t in threads: 
     print("Tell thread to stop: %s", t) 
     queue.put('stop') 
     #t.join() 

if __name__ == '__main__': 
    test() 
+0

Das ist nicht was ich meine. Ich will nicht mehrere Aufgaben und warte auf alle. Ich möchte 1 Aufgabe, nicht warten und habe eine Option, um eine Stop-Anfrage an die Funktion zu senden, die in dieser Aufgabe ausgeführt wird. – Stefan

+0

@Stefan Sie müssen nicht warten, Sie könnten einfach 'print_somedata()' aufrufen und weitermachen. Er hat nur gezeigt, wie Sie auch Zugriff auf den Thread haben könnten, wenn Sie das wollten, was oft wichtig ist (zB wenn Sie * auf * warten müssen, bis die Aufgaben beendet sind oder zumindest prüfen, ob sie fertig sind, was oft der Fall ist) . Die Option, den Thread zu stoppen, kann dem Dekorator nicht hinzugefügt werden, und in der Tat, wenn es möglich wäre, wäre dies keine gute Idee, da dies ein separates Anliegen ist. Aber Sie sagten, Sie möchten, dass die Funktion ordnungsgemäß beendet wird. Sie müssen das also jedes Mal manuell programmieren, weil es nicht immer dasselbe ist. –

+0

@salomonderossi: Es sieht so aus, als ob ich den Rest alleine verstehen könnte, aber ich verstehe es nicht. Wie kann ich einen Stoppbefehl senden? Wenn ich "if stopFlag: return" zwischen alle print & sleep-Anweisungen setze, wie kann ich dies von der Hauptfunktion aus bearbeiten und die Threads stoppen, bevor sie "finished print_somedata" drucken? – Stefan