現在の gevent API を考えると、一般的な解決策はないと思いますが、プラットフォームごとに固有の解決策があると思います。
Posix ソリューション (Linux でテスト済み)
GLib のメイン ループ インターフェイスを使用すると、poll 関数、つまり一連のファイル記述子を取得し、そのうちの 1 つが準備できたときに戻る関数を設定できるため、gevent の select に依存してファイル記述子の準備ができたときに知る poll 関数を定義します。 .
Gevent はpoll()
インターフェイスを公開しておらず、select()
インターフェイスが少し異なるため、呼び出すときに引数と戻り値を変換する必要がありますgevent.select.select()
。
問題を少し複雑にしているのは、GLib が Python のインターフェースを介してg_main_set_poll_func()
、トリックを可能にする特定の関数を公開していないことです。そのため、C 関数を直接使用する必要があります。そのためには、ctypes
モジュールが役立ちます。
import ctypes
from gi.repository import GLib
from gevent import select
# Python representation of C struct
class _GPollFD(ctypes.Structure):
_fields_ = [("fd", ctypes.c_int),
("events", ctypes.c_short),
("revents", ctypes.c_short)]
# Poll function signature
_poll_func_builder = ctypes.CFUNCTYPE(None, ctypes.POINTER(_GPollFD), ctypes.c_uint, ctypes.c_int)
# Pool function
def _poll(ufds, nfsd, timeout):
rlist = []
wlist = []
xlist = []
for i in xrange(nfsd):
wfd = ufds[i]
if wfd.events & GLib.IOCondition.IN.real:
rlist.append(wfd.fd)
if wfd.events & GLib.IOCondition.OUT.real:
wlist.append(wfd.fd)
if wfd.events & (GLib.IOCondition.ERR.real | GLib.IOCondition.HUP.real):
xlist.append(wfd.fd)
if timeout < 0:
timeout = None
else:
timeout = timeout / 1000.0
(rlist, wlist, xlist) = select.select(rlist, wlist, xlist, timeout)
for i in xrange(nfsd):
wfd = ufds[i]
wfd.revents = 0
if wfd.fd in rlist:
wfd.revents = GLib.IOCondition.IN.real
if wfd.fd in wlist:
wfd.revents |= GLib.IOCondition.OUT.real
if wfd.fd in xlist:
wfd.revents |= GLib.IOCondition.HUP.real
ufds[i] = wfd
_poll_func = _poll_func_builder(_poll)
glib = ctypes.CDLL('libglib-2.0.so.0')
glib.g_main_context_set_poll_func(None, _poll_func)
この方法では、使用されている GLib の特定のバージョン/名前を知る必要があるため、より良い解決策があるはずだと思います。g_main_set_poll_func()
これは、GLibが Python で公開されていれば回避できます。また、 を実装する場合は、 をgevent
実装select()
することもできますpoll()
。これにより、このソリューションがよりシンプルになります。
Windows の部分的な解決策 (醜くて壊れている)
Posix ソリューションは Windows では失敗しますselect()
。ネットワーク ソケットでしか機能しないためです。特定の Gtk ハンドルはそうではありません。そこで、別のスレッドで GLib 独自のg_poll()
実装 (Posix ではシン ラッパーであり、Windows ではかなり複雑な実装です) を使用して UI イベントを待機し、TCP ソケットを介してメイン スレッドの gevent 側と同期することを考えました。 . これは、真のスレッド (gevent を使用している場合に使用する可能性のある greenlet は別として) と待機スレッド側のプレーン (非 gevent) ソケットを必要とするため、非常に醜いアプローチです。
残念なことに、Windows の UI イベントはスレッドごとに分割されるため、既定では、1 つのスレッドが別のスレッドのイベントを待機することはできません。特定のスレッドのメッセージ キューは、UI 操作を実行するまで作成されません。そのため、待機中のスレッドに空の WinAPI メッセージ ボックス ( MessageBoxA()
) を作成し (確かにそれを行うより良い方法があります)、AttachThreadInput()
メイン スレッドのイベントを確認できるようにスレッド メッセージ キューをマングルする必要がありました。このすべてを介してctypes
。
import ctypes
import ctypes.wintypes
import gevent
from gevent_patcher import orig_socket as socket
from gi.repository import GLib
from threading import Thread
_poll_args = None
_sock = None
_running = True
def _poll_thread(glib, addr, main_tid):
global _poll_args
# Needed to create a message queue on this thread:
ctypes.windll.user32.MessageBoxA(None, ctypes.c_char_p('Ugly hack'),
ctypes.c_char_p('Just click'), 0)
this_tid = ctypes.wintypes.DWORD(ctypes.windll.kernel32.GetCurrentThreadId())
w_true = ctypes.wintypes.BOOL(True)
w_false = ctypes.wintypes.BOOL(False)
sock = socket()
sock.connect(addr)
del addr
try:
while _running:
sock.recv(1)
ctypes.windll.user32.AttachThreadInput(main_tid, this_tid, w_true)
glib.g_poll(*_poll_args)
ctypes.windll.user32.AttachThreadInput(main_tid, this_tid, w_false)
sock.send('a')
except IOError:
pass
sock.close()
class _GPollFD(ctypes.Structure):
_fields_ = [("fd", ctypes.c_int),
("events", ctypes.c_short),
("revents", ctypes.c_short)]
_poll_func_builder = ctypes.CFUNCTYPE(None, ctypes.POINTER(_GPollFD), ctypes.c_uint, ctypes.c_int)
def _poll(*args):
global _poll_args
_poll_args = args
_sock.send('a')
_sock.recv(1)
_poll_func = _poll_func_builder(_poll)
# Must be called before Gtk.main()
def register_poll():
global _sock
sock = gevent.socket.socket()
sock.bind(('127.0.0.1', 0))
addr = sock.getsockname()
sock.listen(1)
this_tid = ctypes.wintypes.DWORD(ctypes.windll.kernel32.GetCurrentThreadId())
glib = ctypes.CDLL('libglib-2.0-0.dll')
Thread(target=_poll_thread, args=(glib, addr, this_tid)).start()
_sock, _ = sock.accept()
sock.close()
glib.g_main_context_set_poll_func(None, _poll_func)
# Must be called after Gtk.main()
def clean_poll():
global _sock, _running
_running = False
_sock.close()
del _sock
これまでのところ、アプリケーションは実行され、クリックやその他のユーザー イベントに正しく反応しますが、ウィンドウ内には何も描画されません (フレームと背景バッファーが貼り付けられていることがわかります)。スレッドとメッセージ キューのマングリングで、一部の再描画コマンドが欠落している可能性があります。それを修正する方法は私の知識を超えています。何か助けはありますか?それを行う方法について何か良いアイデアはありますか?