On Wed, 2006-18-10 at 23:50 -0500, shawn bright wrote:
> hey there.
> i have a gtk GUI app that uses 4 text views to update info from 4
> different threads. the code i use to update the view is this.
> 
> def buffer_add(self,  input_data, buffer, textview):
>         self.buffer.insert (self.buffer.get_end_iter(), input_data)
>         if self.buffer.get_line_count() > 400:
>             self.buffer.delete(self.buffer.get_start_iter(),
>                                 self.buffer.get_iter_at_line (200))
>         mark = self.buffer.create_mark("end",
>                                 self.buffer.get_end_iter(), False)
>         self.textview.scroll_to_mark(mark, 0.05, True, 0.0, 1.0)
> 
> now, i have the same function in all four threads. Each updates a
> different textview. 
> the threads run in a class threading.thread. 
>  
> the code that calls them is like this.
> 
> S1 = Serial1(self.Input1Buffer, self.TTYS14View)
> S1.start()
> 
> each thread has some initial code like this
> 
> class Serial1(threading.Thread):
>     def __init__(self, buffer, textview):
>         threading.Thread.__init__(self)
>         self.buffer = buffer
>         self.iter = self.buffer.get_end_iter()
>         self.textview = textview
> 
> my question is..... from the main app, if i pass the textview and
> buffer to each thread that needs one, can i, from the thread pass
> those as objects to one function that will update the text view? if
> so, do i need to make that function a global object ? or can i just
> declare it at the beginning of the program ? 
> 
> does this question make sense? 
> 
> if you have read this far, i thank you for your time.
> 
> shawn


Yes, you can pass the buffer pointers around. I have done it many times.
No you do not need to make them global.

Updating any gui items from a thread other than the main gui thread is
asking for trouble.  For any thread to safely access the textview buffer
you need to wrap it with calls to gtk to get clearance (sorry, don't
recall the actual function names atm) to modify them without causing
conflicts.  Something that can hold up your thread waiting for the
update to sync with the gui.

A much safer way to go is to gather your info in the threads and then
pass that info back to the main gui thread to update the correct buffer.
A good example of this is using the dispatcher module we've come up with
that handles the inter-thread communication collision free.  see
attached code.  You will see in the example code that the buffer pointer
is passed to the method that appends the text to the buffer.  It should
be easy to modify the example code to use different buffers for each
thread.

I have used global variables in our app which has several notebook tabs
each with their own textview and use the same append method.  I used
global lists to store the buffer pointers and pass the index number to
the append module as there are several other things that use the index
number as well.  Word of caution if you intend on using a list to store
the pointers use the list.append() to add them to an empty list, don't
initialize the list like:

buffer_list = [None, None] # it won't work

buffer_list = [] # works
buffer_list.append(buffer_pointer1)
buffer_list.append(buffer_pointer2)


code snipit:

    def append(self, num, text, tagname = None):
        """ Append text to a text buffer.  Line numbering based on
            the process window line count is automatically added.
            BUT -- if multiple text buffers are going to be updated,
            always update the process buffer LAST to guarantee the
            line numbering is correct.
            Optionally, text formatting can be applied as well
        """
        #dprint("Notebook: overwrite() -- num= " + str(num))
        #dprint(self.current_tab)
        line_number = self.view_buffer[TAB_PROCESS].get_line_count() 
        iter = self.view_buffer[num].get_end_iter()
        lntext = str(line_number).zfill(6) + ' '
        if self.last_text[num].endswith('\n'):
            self.view_buffer[num].insert_with_tags_by_name(iter, lntext, 
'linenumber')
        if tagname == None:
            #self.view_buffer[num].insert(iter, text)
            #dprint("Notebook: append(): attempting to set text with tagnames " 
+ str(self.current_tagnames))
            self.view_buffer[num].insert_with_tags_by_name(iter, text, 
*self.current_tagnames)
        else:
            self.view_buffer[num].insert_with_tags_by_name(iter, text, tagname)
        if self.auto_scroll[num] and num == self.current_tab:
            self.scroll_current_view()
        self.last_text[num] = text

In the dispatcher example you will see that the method

-- 
Brian <[EMAIL PROTECTED]>
#! /usr/bin/env python
# Fredrik Arnerup <[EMAIL PROTECTED]>, 2004-12-19
# Brian Dolbec<[EMAIL PROTECTED]>,2005-3-30

import gobject, os, Queue
from select import select

class Dispatcher:
    """Send signals from a thread to another thread through a pipe
    in a thread-safe manner"""
    def __init__(self, callback_func, *args, **kwargs):
        self.callback = callback_func
        self.callback_args = args
        self.callback_kwargs = kwargs
        self.continue_io_watch = True
        self.queue = Queue.Queue(0) # thread safe queue
        self.pipe_r, self.pipe_w = os.pipe()
        gobject.io_add_watch(self.pipe_r, gobject.IO_IN, self.on_data)
        
    def __call__(self, *args):
        """Emit signal from thread"""
        self.queue.put(args)
        # write to pipe afterwards
        os.write(self.pipe_w, "X")
    
    def on_data(self, source, cb_condition):
        if select([self.pipe_r],[],[], 0)[0] and os.read(self.pipe_r,1):
            if self.callback_args:
                args = self.callback_args + self.queue.get()
                self.callback(*args, **self.callback_kwargs)
            else:
                self.callback(*self.queue.get(), **self.callback_kwargs)
        return self.continue_io_watch

#! /usr/bin/env python

# Fredrik Arnerup <[EMAIL PROTECTED]>, 2004-12-19
# Brian Dolbec<[EMAIL PROTECTED]>,2005-3-30

import pygtk; pygtk.require("2.0")
import gtk
from time import sleep
import threading, gobject, os
from dispatcher import Dispatcher

# ####################################
# dispatcher
# example code:
#
#
# ####################################

class Thread(threading.Thread):

    def __init__(self, dispatcher, thread_num, length):
        threading.Thread.__init__(self)
        self.setDaemon(1)  # quit even if this thread is still running
        self.dispatcher = dispatcher
        self.thread_num = thread_num
        self.sleep_length = length

    def run(self):
        done = False
        print("thread_num = %s; process id = %d ****************" %(self.thread_num,os.getpid()))
        pid_func(self.thread_num)
        for num in range(250):
            #print self.thread_num, " num = ",num
            sleep(self.sleep_length)
            data = [ self.thread_num, (": time is slipping away: %d\n" %num), num, done]
            self.dispatcher(data) # signal main thread
        done = True
        data = [ self.thread_num, (": Time slipped away: I'm done"), num, done]
        self.dispatcher(data) # signal main thread


def pid_func(threadnum):
    print("pid_func: called from thread_num = %s; process id = %d ****************" %(threadnum,os.getpid()))

def message_fun(buffer, message):
    #print ("got a message : %s" %(message[0] + str(message[1])))
    if message[3]:
        thread_finished[message[0]] = True
        buffer.insert(buffer.get_end_iter(), message[0] + str(message[1]) + "\n\n")
    else:
        #message2 = ("%d x 3 = %d\n" %(message[2],message[2]*3))
        buffer.insert(buffer.get_end_iter(), message[0] + str(message[1])) # + message2)
    return

def timerfunc():
    if (not thread_finished["thread1"]) or (not thread_finished["thread2"]) \
                or (not thread_finished["thread3"]) or (not thread_finished["thread4"]):
        pbar.pulse()
        #print 'Plusing ProgressBar, since a thread is not finished'
        return True
    else:
        pbar.set_fraction(0)
        pbar.set_text("Done")
        return False

def on_window_map_event(event, param):
    print 'Window mapped'
    thread1 = Thread(Dispatcher(message_fun, buffer), "thread1", 0.9)
    thread2 = Thread(Dispatcher(message_fun, buffer), "thread2", 0.9)
    thread3 = Thread(Dispatcher(message_fun, buffer), "thread3", 0.9)
    thread4 = Thread(Dispatcher(message_fun, buffer), "thread4", 0.5)
    gobject.timeout_add(100, timerfunc)
    thread1.start()
    thread2.start()
    thread3.start()
    thread4.start()


if __name__ == "__main__":
       
    gtk.threads_init()
    window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    textview = gtk.TextView()
    buffer = textview.get_buffer()
    sw = gtk.ScrolledWindow()
    sw.add(textview)
    pbar = gtk.ProgressBar()
    vbox = gtk.VBox()
    vbox.pack_start(sw)
    vbox.pack_start(pbar, False)
    window.add(vbox)
    #gui_dispatcher = Dispatcher(message_fun, buffer)
    window.connect('map_event', on_window_map_event)
    window.connect("destroy", gtk.main_quit)
    window.resize(400, 600)
    window.show_all()
    thread_finished = {"thread1":False, "thread2":False, "thread3":False, "thread4":False}
    gtk.threads_enter()
    gtk.main()
    gtk.threads_leave()
_______________________________________________
pygtk mailing list   [email protected]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/

Reply via email to