方案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)