方案1:信号与线程
程序启动,创建一个线程(存活周期:直到软件关闭),当点击事件发生,发送信号,该信号连接两个槽,A负责界面变化切换,B进行后台通讯,B通讯结束,再通过信号将结果返回到界面切换,通过这种机制实现界面与通信的分离。
流程图如下:
结论:
经过代码测试,发现这个方案并非是异步的,而是同步的,原因是同时连接两个槽,这个槽机制应该是一个列表,串行执行的,必然两个槽的执行会存在先后问题,当一个阻塞,另一个也就阻塞了。
方案2:线程
程序启动,当点击事件发生,发送信号,该信号连接一个槽,槽负责界面变化切换,同时创建一个线程(存活周期:报文发送接收完成既关闭),线程进行后台通讯,通讯结束,再通过信号将结果返回到界面切换,通过这种机制实现界面与通信的分离。线程中发送的数据要通过线程创建时传入。
流程图和方案一的流程图是一样的,这时的线程与界面就变成了异步了。
结论:
该方法虽然实现了界面切换与通讯的异步处理,但是每点击一次按钮,都需要一次线程的创建,而且对于一直保持通讯的心跳机制,还需要单独起一个线程,可谓是花费巨大,感觉不是很好的方法
方案3:线程与队列
为了解决方案2中频繁创建线程的问题,现在做如下改进,程序启动,创建一个线程(存活周期:直到软件关闭),在线程中创建多个队列,线程监控队列,队列分别有信号队列,信息发送队列,当前界面位置队列,当界面事件发生,去修改队列,线程则监控队列,取出队列进行处理,处理之后将结果返回
结论:
这样的处理机制避免了线程的频繁创建,同时能存储一些全局的重要信息,也实现了异步的效果。
线程通讯类
'''
@Author: chenjianwen
@Date: 2020-06-03 15:15:27
@LastEditTime: 2020-06-10 17:23:42
@LastEditors: Please set LastEditors
@Description: In User Settings Edit
@FilePath: \DsafeshareClient\communication_module.py
'''
# -*- coding: UTF-8 -*-
from Login_Pane import LoginPane
from Register_Pane import RegisterPane
from Main_Pane import MainMangerPane
from PyQt5.Qt import *
import threading,queue
import time
import sys,signal
import communication.uuser as uuser
import communication.ufile as ufile
from communication.sutils import socket as sutils
import communication.globalv as globalv
'''
@description:
@param {type}
@return:
'''
class communicat_device (QObject,threading.Thread):
'''
需要将发送者的对象先进行注册,然后使用通讯器进行数据发送
通讯器应该是全局可见的,所有设备对象都有调用的权利
'''
send_obj = {}
# recv_obj = {}
'''
@description: post 列表,
@param {type}
@return:
'''
mess_list = []
'''
@description:将执行结果返回给界面进行处理
@param {type}第一个参数为对应的那个指令的返回数据:例如login_recv
第二个参数为返回状态值,参数取值如下:
"dok":返回该字段,代表有数据返回,
"disconnect": 网络错误,第二,三参数为空
"ok": 返回该字段则表示数据正确,该报文没有返回的数据,在报文里,只有一个正确标识,
第三个参数为空
"err":返回该字段则出现错误,第三个字段中则是错误信息
第三个参数返回数据
@return:
'''
sig_recv = pyqtSignal(str,str,str)
'''
@description:
@param {type} 功能栏的信号,id为按下功能键的编号
@return:
'''
sig_recv_id = pyqtSignal(str,str,dict,int)
def __init__(self, threadID, name):
super(communicat_device,self).__init__()
self.threadID = threadID
self.name = name
self.daemon = True
self.exitFlag = False
def __del__(self):
self.wait()
def exit(self):
self.exitFlag = True
def run(self):
while not self.exitFlag:
if len(self.mess_list) > 0:
data = self.mess_list.pop(-1)
meth = self.check_methon(data)
if "login" == meth:
response = sutils.send_cmd(data)
else:
response = sutils.send_cmd_sig(data)
recv_mess = self.check_reply(meth,response)
time.sleep(0.1)
def post(self,data):
self.mess_list.append(data)
'''
@description: 对要发送的数据进行处理,主要是查看mod字段,
是哪一个报文,然后将报文记下来,在回复数据中check_reply,通过该
函数的返回值,进行分别对应处理
@param {type}
@return:
'''
def check_methon(self,meth):
if "login" == meth["mod"]:
return "login"
elif "webdirlist" == meth["mod"]:
return "webdirlist"
pass
def check_reply(self,meth,recv_mess):
if "login" == meth:
if uuser.login(recv_mess) == 1:
self.sig_recv.emit("login_recv","ok","")
else:
self.sig_recv.emit("login_recv","err","")
elif "webdirlist" == meth:
# if ufile.webdirlist(recv_mess) == 1:
# print("xxxxxxxxxxxxx22222:%s",recv_mess)
self.sig_recv_id.emit("webdirlist_recv","ok",recv_mess,0)
# else:
# self.sig_recv_id.emit("webdirlist_recv","err","",0)
pass
实例化调用,当有事件需要发送信息时,在事件clicked中,将数据报文加入到线程的信息列表中,然后线程进行发送,当发送完毕,通过线程中信号发送回来,在调用出用槽接受数据即可
globalv.gl_com_dev = tx_m.communicat_device(0,"xxxx")
globalv.gl_com_dev.start()
def on_check_login_res(recv_mod,flag,mess):
#无论是否正确,都应该关闭loading
if "login_recv" == recv_mod and "ok" == flag :
mian_pane.show()
mian_pane.button_switch_clicked(0)
login_pane.check_close_func()
globalv.gl_menban.setVisible(True)
else:
login_pane.show_error_animation()
globalv.gl_menban.setVisible(False)
def check_login(account, pwd):
globalv.gl_menban.setVisible(True)
globalv.gl_menban.move(login_pane.pos())
globalv.gl_com_dev.post(uuser.login_data(account,pwd,2))
globalv.gl_com_dev.sig_recv.connect(on_check_login_res)
login_pane.check_login_signal.connect(check_login)