4

I have been having problems with trying to create a timer program using python. I want it so that a user can enter an amount of time for the timer to count down from and update every 0.1 seconds or so. So far I have this code:

from gi.repository import Gtk
import time
import threading

class TimerWindow(Gtk.Window):
  def __init__(self):
    Gtk.Window.__init__(self, title = "Timer")

    self.box = Gtk.Box(spacing = 1)
    self.add(self.box)

    self.entry = Gtk.Entry()
    self.entry.connect("activate", self.start, self.entry)
    self.box.pack_start(self.entry, True, True, 0)

  def start(self, widget, entry):
    starttime = time.time()
    totaltime = float(entry.get_text())
    self.update(starttime, totaltime, entry)

  def update(self, starttime, totaltime, entry):
    entry.set_text(str(totaltime - (time.time() - starttime)))
    if float(entry.get_text()) > 0:
      t = threading.Timer(0.1, self.update, [starttime, totaltime, entry])
      t.start()

win = TimerWindow()
win.connect("delete-event", Gtk.main_quit)
win.set_keep_above(True)
win.show_all()
Gtk.main()

This seems to sort of work for a bit, but it sometimes returns this:

timer.py:31: Warning: g_object_ref: assertion 'object->ref_count > 0' failed
  Gtk.main()
Segmentation fault

I do not know what causes this, and I need a bit of help. Could someone help me find a way to stop this from happening?

1 Answer 1

2

Your program is crashing because it is invoking the GTK API from different threads, which is forbidden. Fortunately, it is quite easy to modify it to work correctly - you can use threads, you just need to make sure that all GTK calls are done from the GUI thread, i.e. the thread that runs the main loop. The simplest way to do that is by having your worker threads not execute GUI calls directly, but dispatch them to the main thread using GObject.idle_add. Thus, instead of calling self.update from the timer, call a new method schedule_update, which arranges for the actual update to be called from the GUI thread. Since the GUI thread is not blocked, the update will in effect run immediately:

  def update(self, starttime, totaltime, entry):
    entry.set_text(str(totaltime - (time.time() - starttime)))
    if float(entry.get_text()) > 0:
      t = threading.Timer(0.1, self.schedule_update, [starttime, totaltime, entry])
      t.start()

  def schedule_update(self, *args):
      GObject.idle_add(self.update, *args)

(You will of course need to also import GObject from gi.repository.)

Needless to say, this kind of scheduling is better implemented with GTK main loop timeouts (see GObject.timeout_add), which don't require any additional scheduling because they execute the callback within the GUI thread in the first place. But there are legitimate situations where use of threads is appropriate - e.g. to invoke long-running synchronous APIs such as database access without freezing the GUI, or to perform calculations that internally release the GIL.

Sign up to request clarification or add additional context in comments.

3 Comments

The GObject.timeout_add way works well. When using threads, I keep getting the same errors as before even after adding schedule_update. I may be doing something wrong, but after all, I am pretty new to python threading and stuff, so whatever.
Wait, never mind. I am stupid. I didn't see that the thread started was supposed to run schedule_update instead of update. This answer is 100% correct.
@KZhang If GObject.timeout_add serves your purpose, feel free to forget about threads (for this purpose), since they introduce many complications and problems. Once you gain more experience with Python and GTK, you will be able to use threads safely where necessary.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.