服务时非阻塞GUI阻塞外部I/O——Blender Python支持

目录

1. 引言

2. 问题

3. 场景

4. 解决方案

5. 外部基准电压源

6. 设计

7. 实验输出

7.1 程序说明

7.2 视频

7.3 立方体增删GIF动画

7.4 多维数据集添加和删除代码

8. 它是如何工作的?

9. 进一步增强

10. 进一步的研究


1. 引言

GUI应用程序在回调或异步调用流中工作,这在架构中是事件驱动的。维护事件循环,并逐个执行所有已注册/计划的回调函数。需要处理的事件必须向事件循环管理器注册。回调函数与事件相关联。事件循环中的事件通过执行关联的回调函数来执行。

针对GUI事件调用GUI回调。从事件回调进行阻塞I/O将延迟GUI事件的处理,从而导致GUI冻结。本文讨论如何在GUI框架中以非冻结GUI的方式提供阻塞的外部I/O

2. 问题

如何在GUI相关应用中阻止外部I/O

3. 场景

GUI应用程序在事件回调程序流中工作,而不是顺序程序流,即C编程。具有关联回调函数的事件需要向事件循环管理器注册。事件触发后,事件循环调度程序/管理器将调用关联的回调函数。从任何事件回调函数进行阻塞I/O调用都会冻结GUI,因为程序的执行不会返回到事件循环。禁止事件循环管理器从事件循环调度任何GUI事件。

4. 解决方案

在这个领域有两件事:

  1. I/O通过文件描述符(即套接字)发生。
  2. 与所有其他事件类型一样,还有一个计时器事件,该事件在超时到期时触发。

建议在Timer事件回调中进行I/O操作,其中'select 'I/O描述符多路复用器、api用于以非阻塞方式检查文件描述符集/列表上的读取或写入活动。一旦'select' api返回而没有超时,就会发生I/O。超时将为零,使计时器事件回调完全不阻塞。

5. 外部基准电压源

'select'' API在未提供timeout参数时阻塞,否则在超时时不阻塞。'select' api设备驱动程序实现可以在Linux设备驱动程序第3版一书中找到。在Oreilly出版物下出版。

打开书——Linux设备驱动程序,第3版

高级字符驱动程序操作一章的轮询和选择小节下。而Python实现文档可以在docs.python.org上找到。

select——等待I/O完成

“GUI编程事件机制可以在Xlib编程手册的事件一章中找到。

XLIB编程手册,第5版,第3版

Python中,事件循环是主题异步i/o”中的一个子部分。

asyncio——异步I/O

6. 设计

设计为在事件循环管理器中注册计时器事件。定时器事件回调函数将在非阻塞超时模式下对I/O文件描述符执行select。如果为I/O设置了描述符,则将发生I/O。计时器回调函数将返回事件循环处理或零超时。

7. 实验输出

我们有一个场景,当Blender(一个3D GUI建模工具)需要运行GUI工具外部的Python程序并作为独立进程执行时。默认情况下,Blender支持应用程序内”Python解释器。请遵循下图:

Blender进程中/应用程序Python IDE

我们需要Blender Python支持作为进程/应用程序Python应用程序。像这样:

Python客户端通过套接字连接连接到运行Python服务器代码的Blender

 

Python服务器代码向事件循环管理器注册Timer事件。在计时器事件回调中,Python服务器通过'select ' api调用检查I/O。如果发生超时或设置了描述符(数据到达),则在处理请求后将返回计时器事件回调。客户端和服务器Python代码。

$cat blender_client2.py 
#!/usr/bin/env python 
#blender_client.py script1.py script2.py 
#developed at Minh, Inc. https://youtube.com/@minhinc 
import sys,time,select 
PORT = 8081 
HOST = "localhost" 
def main(): 
 import sys 
 import socket 
 if len(sys.argv)<2: 
 print(f'---usage---\npython3 blender_client.py blenderpythonscript.py blenderpythonscript2.py ..') 
 sys.exit(-1)
 clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 try: 
 clientsocket.connect((HOST, PORT)) 
 except Exception as exc: 
 print(f'E Error in connection with server, probably try after sometime/minutes... Exception type -
> {type(exc)=}') 
 sys.exit(-1)
 else: 
 print(f'I blender_client connection to server successful') 
 filestosend=' '.join(sys.argv[1:]) 
 print(f'sending file(s) -> {filestosend} to the server') 
 clientsocket.sendall(filestosend.encode("utf-8") + b'\x00') 
 print(f'I blender_client message sent successfully, waiting for response..') 
 while True: 
 messagerecved=clientsocket.recv(4096) 
 if not messagerecved: 
 print(f'Empty message received, sleeping for 10 secs...') 
 time.sleep(10) 
 else: 
 print(f'Message received {messagerecved=}, exiting...')
 clientsocket.close() 
 break 
if __name__ == "__main__": 
 main() 
$cat blender_server2.py 
#blender --python blender_server.py 
#developed at Minh, Inc. https://youtube.com/@minhinc 
import socket,time,select,re,datetime 
import bpy 
PORT = 8081 
HOST = "localhost" 
PATH_MAX = 4096 
def execfile(filepath): 
 import os 
 global_namespace = { 
 "__file__": filepath, 
 "__name__": "__main__", 
 } 
 with open(filepath, 'rb') as file: 
 exec(compile(file.read(), filepath, 'exec'), global_namespace) 
def main(): 
 global serversocket,read_list,file_list,connection,result_list 
 file_list=[] 
 result_list=[] 
 connection=None 
 serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 serversocket.bind((HOST, PORT)) 
 serversocket.listen(5) #accept upto 6 connect and messages 
 print("Listening on %s:%s" % (HOST, PORT)) 
 read_list=[serversocket] 
def handle_data(): 
 global file_list,connection,result_list 
 timeout=20 
 def send_data(): 
 nonlocal timeout 
 print(f'I blender_server executing file {file_list[0]} full {file_list=} ') 
 try: 
 execfile(file_list[0]) 
 print(f'executed successfully {file_list[0]=}') 
 result_list.append(f'{file_list[0]} - success') 
 except Exception as exc: 
 print(f'Error while executing {file_list[0]=} {exc=}') 
 result_list.append(f'{file_list[0]} - failed exception {exc}') 
 file_list[0:1]=[] 
 timeout=2 
 if file_list: 
 send_data() 
 else: 
 if connection: 
 connection.sendall('\n'.join(result_list).encode('utf-8') + b'\x00') 
 print("response ",'\n'.join(result_list)," sent to client") 
 connection.close() 
 connection=None 
 result_list=[] 
 readable,writable,errored=select.select(read_list,[],[],0.0) 
 print(f'E handle_data() {(readable,writable,errored)=} {read_list=} at time -> 
{datetime.datetime.now():%H:%M:%S}') 
 for s in readable: 
 if s in read_list: 
 connection, address = serversocket.accept() 
 print(f'I blender_server connection accepted {connection=} {address=}') 
 file_list = re.split(r'\s+',re.split(b'\x00',connection.recv(PATH_MAX))[0].decode()) 
 print(f'I blender_server data received {file_list=}')
 send_data() 
 print(f'handle_data, returning {timeout} second timeout..') 
 return timeout 
if __name__ == "__main__": 
 main() 
 bpy.app.timers.register(handle_data) 

脚本可以在两个终端中执行,如下所示:

1、第一个终端

Blender — Python blender_server.py

2、第二终端

python3 blender_client.py <pythonprogram1> <pythonprogram2> <pythonprogram3>

下图用于打开两个终端:

客户端和服务器各有双终端

7.1 程序说明

客户端程序接受多个Python程序作为命令行参数。程序名称以string形式加入并发送到服务器。服务器解析请求并逐个处理每个Python文件。每次它从计时器事件循环返回时,事件循环管理器将有机会处理其他事件。

Python程序以串联string形式发送到Python服务器。服务器进程一个接一个。在每次文件处理后返回事件循环。

7.2 视频

关于为客户端和服务器分叉终端的视频。

视频——从客户端到服务器触发python脚本。

7.3 立方体增删GIF动画

通过客户端应用程序触发的脚本添加和删除多维数据集的GIF动画

7.4 多维数据集添加和删除代码

$ cat cubeadd_y.py 
import bpy 
import bmesh 
import mathutils 
bm = bmesh.new() 
bmesh.ops.create_cube(bm, size=4) 
mesh = bpy.data.meshes.new('Basic_Cube') 
bm.to_mesh(mesh) 
mesh.update() 
bm.free() 
basic_cube = bpy.data.objects.new("Basic_Cube", mesh) 
basic_cube.matrix_world.translation += basic_cube.matrix_world.to_3x3() @ 
mathutils.Vector((0.0,6.0,0.0)) 
bpy.context.collection.objects.link(basic_cube) 
$ cat cubeadd_x.py 
import bpy 
import bmesh 
import mathutils 
bm = bmesh.new() 
bmesh.ops.create_cube(bm, size=4) 
mesh = bpy.data.meshes.new('Basic_Cube') 
bm.to_mesh(mesh) 
mesh.update() 
bm.free() 
basic_cube = bpy.data.objects.new("Basic_Cube", mesh)
basic_cube.matrix_world.translation += basic_cube.matrix_world.to_3x3() @ mathutils.Vector((-
6.0,0.0,0.9)) 
bpy.context.collection.objects.link(basic_cube) 
$ cat cubedelete.py 
import bpy 
import mathutils 
try:
 cube = bpy.data.objects['Cube'] 
 bpy.data.objects.remove(cube, do_unlink=True) 
except: 
 print("Object bpy.data.objects['Cube'] not found") 
 
bpy.ops.outliner.orphans_purge() 

8. 它是如何工作的?

两个Python程序客户端和服务器通过套接字进程间通信相互交互。套接字也可用于计算机间通信。IP地址需要是服务器的真实IP地址。Blender以脚本模式启动,并以' — python'作为命令行参数。Blender在主线程中启动python程序。Python程序不执行任何任务,而是注册一个计时器事件以事件循环代码。

交互流的活动图

'bpy.app.timers.register(handle_data)''handle_event'作为回调函数传递。事件回调函数'handle_data'在主循环中调用,它使用'select'作为I/O多路复用器,以非阻塞模式处理I/O。一旦连接到达读取描述符已设置,就会读取并处理连接请求。如果有多个文件,Timer回调将返回(到事件主循环),超时为0秒。在这里,2秒用于使解释更加直观。返回到每个Python脚本文件处理之间的事件循环,使事件循环管理器有机会执行其他GUI事件,使GUI看起来是交互式的。

9. 进一步增强

客户端Python脚本可以通过IDE编辑器进行编辑。编辑器将具有GUI按钮选项和上下文菜单选项来执行脚本。

建议的IDE编辑器,带有单独的工具栏按钮和上下文菜单来执行脚本

与服务器的数据/字符串通信将显示在“docket”窗口中。

10. 进一步的研究

  1. UNIX环境中的高级编程,W. Richard Stevens。Addison-Wesley专业计算系列
  2. TCP/IP图解,W. Richard Stevens:协议第1卷,实现第2卷,TCP for Transactions、HTTP、NNTP和UNIX域协议第3卷

https://www.codeproject.com/Articles/5375662/Non-blocking-GUI-while-Serving-Blocking-External-I

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值