Welcome, guest | Sign In | My Account | Store | Cart

A mechanism for communication from any thread to the main thread.

Python, 78 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#idle_queue.py

import Queue

#Global queue, import this from anywhere, you will get the same object.
idle_loop = Queue.Queue()

def idle_add(func, *args, **kwargs):
    #use this function to add your callbacks/methods
    def idle():
        func(*args, **kwargs)
        return False
    idle_loop.put(idle)


#idle_queue_dispatcher.py

from PySide.QtGui import *
from PySide.QtCore import *

from idle_queue import idle_loop


class ThreadDispatcher(QThread):
    def __init__(self, parent):
        QThread.__init__(self)
        self.parent = parent

    def run(self):
        while True:
            callback = idle_loop.get()
            if callback is None:
                break
            QApplication.postEvent(self.parent, _Event(callback))

    def stop(self):
        idle_loop.put(None)
        self.wait()


class _Event(QEvent):
    EVENT_TYPE = QEvent.Type(QEvent.registerEventType())

    def __init__(self, callback):
        #thread-safe
        QEvent.__init__(self, _Event.EVENT_TYPE)
        self.callback = callback


#main.py

from PySide.QtGui import *
from PySide.QtCore import *

from idle_queue_dispatcher import ThreadDispatcher


class Gui(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        
        #....
        
        self.dispatcher = ThreadDispatcher(self)
        self.dispatcher.start()
        
        self.show()

    def customEvent(self, event):
        #process idle_queue_dispatcher events
        event.callback()


if __name__ == "__main__":
    app = QApplication(['']) #QApplication(sys.argv)
    gui = Gui()
    app.exec_()
    gui.dispatcher.stop()

You may use it for putting methods/functions (whatever callable object) in the queue, and it will be called (as soon as possible) in the main thread. Specially useful if you want to interact with the GUI.

You may want to integrate this recipe, with the following events/signals dispatcher: http://code.activestate.com/recipes/578307-python-a-eventsignal-dispatcher/

4 comments

Justin Israel 8 years, 4 months ago  # | flag

I was just about to put something together that is between this and your dispatcher that uses weak bounded refs to methods, and came across this example. I am curious about why the Threaded dispatcher is even necessary here? All it is doing is taking callables off the queue and posting them to the main event loop. But postEvent() is not a blocking call and does not doing anything immediately. How would this be any different then just having a function like:

postCallback(receiver, callback)

And this would wrap it in the event and post a weakMethod callback? What does the threaded dispatcher provide that I am missing here?

Esteban Castro Borsani (author) 8 years, 4 months ago  # | flag

"What does the threaded dispatcher provide that I am missing here?"

If you are only posting events from the view layer (pyQT code), it would not be necessary. You would be better off using pyQT custom signals, anyway.

This code is meant to be integrated with an event dispatcher , so you can send/post events from any other layer of your MVC application, the Django way.

Also, this allows you to have multiple UIs (ex: CLI, Web UI, GUI) as long as you write your application MVC compliant. You may use this same pattern on GTK, WXPython, etc.

Justin Israel 8 years, 3 months ago  # | flag

I am still failing to understand this threaded dispatcher even under the circumstance that you are letting other layers of code register callbacks to be dispatched. This code is primary surrounding Qt's event loop. If postEvent() is not blocking anyways, what would be the difference if you simply had your idle_add() function directly call postEvent() instead of having it run through a thread that is just pulling them right back out and putting them into the event loop?

Esteban Castro Borsani (author) 8 years, 3 months ago  # | flag

"what would be the difference if you simply had your idle_add() function directly call postEvent() instead of having it run through a thread that is just pulling them right back out and putting them into the event loop?"

It'd work both ways.

But, you'd have to import pyQt in your controller or import the view in your controller, depending on where you put the idle_add() code. You'd be coupling the logical layer to your view/qt in the wrong way. You could also monkey patch, that's worse.

I guess you may add some kind of top level module/middleware, where you put the idle_add function:

# dummy non-working code
def idle_add(func):
    if is_cli_mode:
        put_on_cli_queue(func)
    elif is_webui_mode:
        put_on_webui_queue(fun)
    else:
        pyQt.postEvent(func)

Honestly, I did not think of that until now. Still, I like the threaded dispatcher better. idle_loop.get() is a blocking call so it's not doing any CPU intensive work.