Gtk是一个GUI工具包,绑定到 Python. Gevent是一个基于libevent(新版本上的libev)和greenlets构建的Python网络库,允许在greenlet中使用网络功能而不会阻塞整个过程.

Gtk和gevent都阻塞了调度事件的主循环.如何集成他们的主循环,以便我可以在我的应用程序上接收网络事件和UI事件,而不会阻止另一个?

天真的方法是在Gtk的主循环上注册一个空闲回调,只要没有Gtk事件就会调用它.在这个回调中,我们产生了greenlet,因此可以发生网络事件,同时给出一个小的超时,因此进程不忙等待:

from gi.repository import GLib
import gevent

def _idle():
    gevent.sleep(0.1)
    return True

GLib.idle_add(_idle)

这种方法远非理想,因为我在UI事件处理之间有100毫秒的延迟,如果我将值降低太多,我会浪费太多处理器忙等待.

我想要一个更好的方法,我的过程真的睡着了,而没有事件需要处理.

PS:我已经找到了一个特定于Linux的解决方案(也可能在MacOS下工作).我现在真正需要的是一个可行的Windows解决方案.

解决方法

鉴于当前的gevent API,我认为没有通用的解决方案,但我认为每个平台可能都有特定的解决方案.

Posix解决方案(在Linux下测试)

由于GLib的主循环接口允许我们设置轮询函数,即获取一组文件描述符并在其中一个准备就绪时返回的函数,我们定义一个轮询函数,该函数依赖于gevent的select来知道文件描述符何时准备好.

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,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的特定版本/名称.如果GLib在Python中暴露了g_main_set_poll_func(),则可以避免这种情况.此外,如果gevent实现select(),它可以很好地实现poll(),什么会使这个解决方案更简单.

Windows部分解决方案(丑陋和破碎)

Posix解决方案在Windows上失败,因为select()仅适用于网络套接字,而Gtk处理的内容则不然.所以我想在另一个等待UI事件的线程中使用GLib自己的g_poll()实现(Posix上的一个瘦包装器,在Windows上是一个相当复杂的实现),并通过主线程中的gevent方面同步它一个TCP套接字.这是一个非常难看的方法,因为它需要真正的线程(除了你可能会使用的greenlets,如果你使用gevent)和等待线程端的普通(非gevent)套接字.

Windows上太糟糕的UI事件由线程分割,因此默认情况下,一个线程不能等待另一个线程上的事件.在执行某些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,w_false)
            sock.send('a')
    except IOError:
        pass
    sock.close()

class _GPollFD(ctypes.Structure):
    _fields_ = [("fd",ctypes.c_short)]

_poll_func_builder = ctypes.CFUNCTYPE(None,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,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

到目前为止,应用程序运行并对点击和其他用户事件做出正确反应,但窗口内没有任何内容(我可以看到框架和背景缓冲区粘贴到其中).在线程和消息队列的重整中可能缺少某些重绘命令.我不知道如何修复它.有帮助吗?关于如何做的更好的想法?

如何将Python的GTK与gevent集成?的更多相关文章

  1. 吃透移动端 Html5 响应式布局

    这篇文章主要介绍了吃透移动端 Html5 响应式布局,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. iOS:核心图像和多线程应用程序

    我试图以最有效的方式运行一些核心图像过滤器.试图避免内存警告和崩溃,这是我在渲染大图像时得到的.我正在看Apple的核心图像编程指南.关于多线程,它说:“每个线程必须创建自己的CIFilter对象.否则,你的应用程序可能会出现意外行为.”这是什么意思?我实际上是试图在后台线程上运行我的过滤器,所以我可以在主线程上运行HUD(见下文).这在coreImage的上下文中是否有意义?

  3. ios – 多个NSPersistentStoreCoordinator实例可以连接到同一个底层SQLite持久性存储吗?

    我读过的关于在多个线程上使用CoreData的所有内容都讨论了使用共享单个NSPersistentStoreCoordinator的多个NSManagedobjectContext实例.这是理解的,我已经使它在一个应用程序中工作,该应用程序在主线程上使用CoreData来支持UI,并且具有可能需要一段时间才能运行的后台获取操作.问题是NSPersistentStoreCoordinator会对基础

  4. ios – XCode断点应该只挂起当前线程

    我需要调试多线程错误.因此,为了获得生成崩溃的条件,我需要在代码中的特定点停止一个线程,并等待另一个线程到达第二个断点.我现在遇到的问题是,如果一个线程遇到断点,则所有其他线程都被挂起.有没有办法只停止一个线程,让其他线程运行,直到它们到达第二个断点?)其他更有趣的选择:当你点击第一个断点时,你可以进入控制台并写入这应该在该断点处暂停当前上下文中的线程一小时.然后在Xcode中恢复执行.

  5. ios – 在后台线程中写入Realm后,主线程看不到更新的数据

    >清除数据库.>进行API调用以获取新数据.>将从API检索到的数据写入后台线程中的数据库中.>从主线程上的数据库中读取数据并渲染UI.在步骤4中,数据应该是最新数据,但我们没有看到任何数据.解决方法具有runloops的线程上的Realm实例,例如主线程,updatetothelatestversionofthedataintheRealmfile,因为通知被发布到其线程的runloop.在后台

  6. 如何在iOS上快速将ALAsset映像保存到磁盘?

    我正在使用ALAsset来检索这样的图像:这返回CGImageRef,我想尽快保存到磁盘…解决方案1:解决方案2:问题是两种方法在设备上的执行速度都很慢.每张图片大约需要2秒才能执行此操作.这绝对是长久的.问题:如何加快图像保存过程?或许还有更好的解决方案吗?

  7. ios – NSURLConnectionLoader线程中的奇怪崩溃

    我们开始看到我们的应用启动时发生的崩溃.我无法重现它,它只发生在少数用户身上.例外情况是:异常类型:EXC_BAD_ACCESS代码:KERN_INVALID_ADDRESS位于0x3250974659崩溃发生在名为com.apple.NSURLConnectionLoader的线程中在调用时–[NSBlockOperationmain]这是该线程的堆栈跟踪:非常感谢任何帮助,以了解可能导致这种崩

  8. ios – 合并子上下文时的NSObjectInaccessbileExceptions

    我尝试手动重现,但失败了.是否有其他可能发生这种情况的情况,是否有处理此类问题的提示?解决方法在创建子上下文时,您可以尝试使用以下行:

  9. ios – 从后台线程调用UIKit时发出警告

    你如何处理项目中的这个问题?

  10. ios – 在SpriteKit中,touchesBegan在与SKScene更新方法相同的线程中运行吗?

    在这里的Apple文档AdvancedSceneProcessing中,它描述了更新方法以及场景的呈现方式,但没有提到何时处理输入.目前尚不清楚它是否与渲染循环位于同一个线程中,或者它是否与它并发.如果我有一个对象,我从SKScene更新方法和touchesBegan方法(在这种情况下是SKSpriteNode)更新,我是否要担心同步对我的对象的两次访问?解决方法所以几天后没有回答我设置了一些实验

随机推荐

  1. 10 个Python中Pip的使用技巧分享

    众所周知,pip 可以安装、更新、卸载 Python 的第三方库,非常方便。本文小编为大家总结了Python中Pip的使用技巧,需要的可以参考一下

  2. python数学建模之三大模型与十大常用算法详情

    这篇文章主要介绍了python数学建模之三大模型与十大常用算法详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感想取得小伙伴可以参考一下

  3. Python爬取奶茶店数据分析哪家最好喝以及性价比

    这篇文章主要介绍了用Python告诉你奶茶哪家最好喝性价比最高,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧

  4. 使用pyinstaller打包.exe文件的详细教程

    PyInstaller是一个跨平台的Python应用打包工具,能够把 Python 脚本及其所在的 Python 解释器打包成可执行文件,下面这篇文章主要给大家介绍了关于使用pyinstaller打包.exe文件的相关资料,需要的朋友可以参考下

  5. 基于Python实现射击小游戏的制作

    这篇文章主要介绍了如何利用Python制作一个自己专属的第一人称射击小游戏,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起动手试一试

  6. Python list append方法之给列表追加元素

    这篇文章主要介绍了Python list append方法如何给列表追加元素,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  7. Pytest+Request+Allure+Jenkins实现接口自动化

    这篇文章介绍了Pytest+Request+Allure+Jenkins实现接口自动化的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  8. 利用python实现简单的情感分析实例教程

    商品评论挖掘、电影推荐、股市预测……情感分析大有用武之地,下面这篇文章主要给大家介绍了关于利用python实现简单的情感分析的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下

  9. 利用Python上传日志并监控告警的方法详解

    这篇文章将详细为大家介绍如何通过阿里云日志服务搭建一套通过Python上传日志、配置日志告警的监控服务,感兴趣的小伙伴可以了解一下

  10. Pycharm中运行程序在Python console中执行,不是直接Run问题

    这篇文章主要介绍了Pycharm中运行程序在Python console中执行,不是直接Run问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

返回
顶部