Skip to main content

本框架旨在将运行时间较长的方法在一个线程中执行,最后通过插槽将结果返回给界面,其中涉及 socket 的通信,Qt多线程,Qt插槽,以及任务注册的概念。

Project description

PySide6 Socket 异步框架

作者:chakcy

简介:利用 socket、threding 和 Qt 插槽实现的异步通信框架。

仓库地址:https://gitee.com/cai-xinpenge/pyside_socket_async.git

目录

[TOC]

介绍

本框架旨在将运行时间较长的方法在一个线程中执行,最后通过插槽将结果返回给界面,其中涉及 socket 的通信,Qt多线程,Qt插槽,以及任务注册的概念。

主要实现思路

在开启 pyside 应用的同时启动另一个线程,该线程启动一个socket 服务,用于与界面进行交互。

在开启 socket 服务线程前,动态的检测某路径下的模块,将带有 @task_function 装饰器的方法动态的生成 TSItem类,并将它们添加到 TS 类的 services 和 tasks 中,tasks 会被初始化到 socket 线程中作为待调用的方法,而 services 则是主线程发送调用方法的指令,即但主线程调用 services 中的某个方法后,会向 socket 服务发送一个请求,该请求无需等待结果,socket 服务会开启一个线程调用对应的 task 方法,执行完成后,将结果通过插槽返回给主线程,TS 会通过某种机制将结果返回给对应 service 的回调函数。

TS类的设计与回调管理

  1. 回调函数的注册:
    • 在 TS 类中,有一个字典用于管理回调函数。每当主线程调用某个 service 的方法时,框架会生成一个唯一的标识符(UUID),作为字典中的键,将对应的回调函数作为值存入字典中。
    • 这种机制允许框架在执行任务时追踪每个请求和其对应的回调。
  2. 任务执行与回调:
    • 当任务被发送到 socket 服务并执行时,服务会在任务执行完成后,构建一个结果对象,这个对象会包含之前生成的 UUID。
    • 框架使用 Qt 的插槽机制将结果发送回主线程。
  3. 回调函数的调用:
    • 主线程接收到结果后,会利用结果中的 UUID 查询 TS 类中存储的字典,找到对应的回调函数。
    • 找到回调函数后,框架会执行该函数,并将结果传递给它。
  4. 清理工作:
    • 在回调函数执行完毕后,框架会将字典中对应的键值对(即 UUID 和回调函数的对应关系)删除,一避免内存泄漏和保持字典的简洁。

依赖

pydantic>=2.9.1
PySide6>=6.7.2
requests>=2.32.3

使用案例

安装

mkdir demo
git clone https://gitee.com/cai-xinpenge/pyside_socket_async.git
cd pyside_socket_async
pip install .

目录结构

demo
├── app
│   ├── __init__.py
│   ├── engine
│   │   ├── __init__.py
│   │   ├── service
│   │   │   ├── __init__.py
│   │   │   └── test.py
│   │   └── task
│   │       ├── __init__.py
│   │       └── test.py
│   ├── interface
│   │   ├── __init__.py
│   │   └── window.py
│   ├── model
│   │   ├── __init__.py
│   │   └── http_result.py
│   ├── utils
│   │   ├── __init__.py
│   │   └── http_client.py
│   └── views
│       ├── __init__.py
│       └── window.py
└── main.py

main.py

import sys
from app import app, window_view

if __name__ == "__main__":
    window_view.show()
    sys.exit(app.exec())

app/__init__.py

import sys

from PySide6 import QtWidgets
from pyside_socket_async.socket_thread import create_socket_server_thread

from .views import MainWindow
from .engine import task_service

app = QtWidgets.QApplication(sys.argv)
window_view = MainWindow()

# 启动 socket 服务线程
create_socket_server_thread(task_service, app)

app/engine/__init__.py

from pyside_socket_async.tasks_service import discover_and_mount_ts_item


# 自动发现并挂载 task 类
task_service = discover_and_mount_ts_item('app.engine.task')
print(task_service.get_all_task_names())

app/engine/service/__init__.py

from .test import TestService 


__all__ = ['TestService']

app/engine/service/test.py

from typing import Union, Any

from PySide6.QtWidgets import QMessageBox
from requests import Response
from pyside_socket_async import ServiceBase
from pyside_socket_async.model import Result, ResponseError

from app.interface import MainWindowInterface


class TestService(ServiceBase):
    @classmethod
    def request(cls, data, window:Union[MainWindowInterface, Any]):
        cls.window = window
        cls.window.pushButton.setText("请稍等")
        cls.window.pushButton.setEnabled(False)
        return super().request("test", data)
    
    @classmethod
    def callback(cls, result: Result):
        response: Union[ResponseError, Response] = result.result
        if response.status_code / 100 >= 3:
            QMessageBox.warning(cls.window, "错误", f"{response.status_code}请求失败") # type: ignore
            cls.window.pushButton.setEnabled(False)
            cls.window.pushButton.setText("点击我")
            return
        
        print(f"返回结果为:{response}")
        # cls.window.pushButton.setText(f"{response.result}")
        cls.window.pushButton.setEnabled(True)
        cls.window.pushButton.setText("点击我")

app/engine/task/__init__.py

from .test import test


__all__ = ['test']

app/engine/task/test.py

import time

from pyside_socket_async import task_function
from app.utils import httpClient


@task_function
import time

from pyside_socket_async import task_function
from app.utils import httpClient


@task_function
def test(args):
    time.sleep(5)
    result = httpClient.get("/")
    if result.status_code == 200: 
        with open("test.txt", "w") as f:
            f.write(result.response.text) # type: ignore

    return result
    

app/interface/__init__.py

from .window import MainWindowInterface


__all__ = ["MainWindowInterface"]

app/interface/window.py

from PySide6.QtWidgets import QPushButton


# 定义接口类,方便在service中调用界面元素
class MainWindowInterface:
    def __init__(self):
        self.pushButton: QPushButton

app/utils/__init__.py

from .http_client import httpClient


__all__ = ['httpClient']

app/utils/http_client.py

from pyside_socket_async.utils import HttpClient


httpClient = HttpClient(base_url="https://www.baidu.com")

app/views/__init__.py

from .window import MainWindow


__all__ = ["MainWindow"]

app/views/window.py

from PySide6.QtWidgets import QPushButton, QMainWindow, QWidget

from pyside_socket_async.model import Result
# from pyside_socket_async import send_data

from ..engine.service import TestService


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__()
        self.centralwidget = QWidget(self)
        self.init_ui()
        self.bind()

    def init_ui(self):
        self.pushButton = QPushButton(self.centralwidget)
        self.pushButton.setText("点击我")

    def bind(self):
        self.pushButton.clicked.connect(self.test)
   
    def test(self):
        # 调用 service 类中的 request 方法,并将回调函数设置为 cls.callback
        TestService().request({}, self)

    # 建议使用 service 类中的 request 方法,简单的界面操作也可使用 send_data 方法
    # def test(self):
    #     def callback(data: Result):
    #         print(f"返回结果为:{data.result}")
    #         self.pushButton.setText(f"{data.result}")
        
    #     send_data("test", {}, callback)

执行

python main.py

执行

点击按钮

点击按钮

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyside_socket_async-0.2.0.tar.gz (16.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyside_socket_async-0.2.0-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

Details for the file pyside_socket_async-0.2.0.tar.gz.

File metadata

  • Download URL: pyside_socket_async-0.2.0.tar.gz
  • Upload date:
  • Size: 16.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.4

File hashes

Hashes for pyside_socket_async-0.2.0.tar.gz
Algorithm Hash digest
SHA256 bba1f6996cedbce7d3da28f19cfd86ff6a05815ce14453ea28c9d416f7e1aaf5
MD5 f8145fd1f0fafe7090713cf4ed4d5116
BLAKE2b-256 4f83482e3c2a12fbc076091c1a76bec19aad946617449ac12253257d44204210

See more details on using hashes here.

File details

Details for the file pyside_socket_async-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pyside_socket_async-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5f933c0eacdece94abdadd4c0a9434ed2cbdeb8124fdbed8def632fa1adb0030
MD5 8634b0c7bb9d8db02479b2dde254a35b
BLAKE2b-256 a362bb4f41809f616be4a281f0452da6a02224378b7fd1386034b6dfab6c45a5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page