g_idle_add

Gedit for Windows part3 | PaePoi
を少し改造しようと調べている時に g_idle_add という GLib の関数を知った。
筆者は何も知らないな、もっと勉強しなければいけないようだ。

何も処理を行っていないアイドル時にシグナルを送りつける。
ハンドラにて True を戻すとループ継続、False を戻すとループが止る。
g_timeout_add と同様みたい、ちとテスト。

#!/usr/bin/env python3

from gi.repository import Gtk, GLib

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.label = Gtk.Label("0")
        button = Gtk.Button.new_with_label("Time consuming process")
        button.connect("clicked", self.on_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(self.label, False, False, 0)
        vbox.pack_start(button, False, False, 0)
        self.add(vbox)
        self.show_all()
        # var
        self.count = 0
        # Idle
        GLib.idle_add(self.on_idle)

    def on_idle(self):
        if self.count == 1000000:
            return False
        self.count += 1
        self.label.set_text("{0}".format(self.count))
        return True

    def on_clicked(self, widget, data=None):
        s = ""
        for i in range(10000000):
            s += "homura"

IdleTest()
Gtk.main()

g_idle_add1

ボタンを押して何か処理を行っている最中は見事にシグナルは停止する。
なるほど、同一プロセスで何もしていない時だけシグナルが発生するのか。

#!/usr/bin/env python3

from gi.repository import Gtk, GLib

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.label1 = Gtk.Label("0")
        self.label2 = Gtk.Label("0")
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(self.label1, False, False, 0)
        vbox.pack_start(self.label2, False, False, 0)
        self.add(vbox)
        self.show_all()
        # var
        self.count1 = 0
        self.count2 = 0
        # Idle
        GLib.idle_add(self.on_idle)

    def on_idle(self):
        if self.count1 == 100000:
            return False
        if self.count1 == 50000:
            GLib.idle_add(self.on_idle2)
        self.count1 += 1
        self.label1.set_text("{0}".format(self.count1))
        return True

    def on_idle2(self):
        if self.count2 == 100000:
            return False
        self.count2 += 1
        self.label2.set_text("{0}".format(self.count2))
        return True

IdleTest()
Gtk.main()

2 つ作成しても別スレッドとして動作するみたいですね。
上記のような使い方は CPU 負荷が凄いのでヤメたほうがいいと一応。

これがどういう時に便利かというと簡易スレッド分離。
ランチャを作成し、起動したことを書き出す。
そして終了コードを調べて表示したい等とする。

#!/usr/bin/env python3

from gi.repository import Gtk
import subprocess

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        view = Gtk.TextView()
        view.set_size_request(200, 100)
        self.buf = view.get_buffer()
        button = Gtk.Button.new_with_label("gnome-calculator")
        button.connect("clicked", self.on_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(view, True, True, 0)
        vbox.pack_end(button, False, False, 0)
        self.add(vbox)
        self.show_all()

    def on_clicked(self, widget, data=None):
        self.buf.set_text("Do gnome-calculator\n\n")
        #
        it = self.buf.get_end_iter()
        retcode = subprocess.call(["gnome-calculator"])
        if retcode == 0:
            self.buf.insert(it, "Sucsess")
        else:
            self.buf.insert(it, "Error: {0}".format(retcode))

IdleTest()
Gtk.main()

動きそうな気がするけど実際は on_clicked を抜けるまで何も書き出されない。
なので on_clicked にて g_idle_add を入れてスレッドを別にする。

#!/usr/bin/env python3

from gi.repository import Gtk, GLib
import subprocess

class IdleTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        view = Gtk.TextView()
        view.set_size_request(200, 100)
        self.buf = view.get_buffer()
        button = Gtk.Button.new_with_label("gnome-calculator")
        button.connect("clicked", self.on_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(view, True, True, 0)
        vbox.pack_end(button, False, False, 0)
        self.add(vbox)
        self.show_all()

    def on_clicked(self, widget, data=None):
        self.buf.set_text("Do gnome-calculator\n\n")
        GLib.idle_add(self.on_idle)

    def on_idle(self):
        it = self.buf.get_end_iter()
        retcode = subprocess.call(["gnome-calculator"])
        if retcode == 0:
            self.buf.insert(it, "Sucsess")
        else:
            self.buf.insert(it, "Error: {0}".format(retcode))
        return False

IdleTest()
Gtk.main()

g_idle_add2

これなら on_clicked は抜けランチャの終了待機も問題なく行われる。
つまりいつ終るか解らない処理を待つ必要がある場合に利用できる。
こんな感じで簡易なスレッドが必要な場合はもうコレで充分ですよね。