qthreadwithreturn


Nameqthreadwithreturn JSON
Version 1.1.1 PyPI version JSON
download
home_pageNone
SummaryPySide6 高级线程工具库 - 带返回值的线程类和线程池执行器
upload_time2025-08-12 08:43:04
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseNone
keywords qt pyside6 threading concurrent futures gui pyqt async thread-pool worker-thread
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <div align="center">

# QThreadWithReturn

![QThreadWithReturn](https://socialify.git.ci/271374667/QThreadWithReturn/image?description=1&language=1&name=1&pattern=Plus&theme=Auto)

[![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://python.org)
[![PySide6](https://img.shields.io/badge/PySide6-6.4+-green.svg)](https://www.qt.io/qt-for-python)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Tests](https://img.shields.io/badge/Tests-73%20passed-brightgreen.svg)](tests/)

一个基于 PySide6 的线程工具库,简化 GUI 应用中的多线程编程。

QThreadWithReturn 为传统 QThread 提供了更直观的 API,支持返回值和回调机制,避免复杂的信号槽设置。

</div>

## ✨ 特性

### 🎯 QThreadWithReturn
- 支持获取线程执行结果,提供类似 `concurrent.futures.Future` 的 API
- 灵活的回调机制,支持多种回调函数签名
- 内置超时控制和任务取消
- 线程安全的状态管理
- 与 Qt 事件循环无缝集成

### 🏊‍♂️ QThreadPoolExecutor
- 完全兼容 `concurrent.futures.ThreadPoolExecutor` API
- 线程池管理和任务调度
- 支持线程初始化器和命名,便于调试
- 支持 `as_completed` 等标准接口
- 上下文管理器支持

## 🚀 安装

```bash
# 使用 uv
uv add qthreadwithreturn

# 使用 pip  
pip install qthreadwithreturn
```

## 💡 使用示例

### 问题场景

在 GUI 应用中执行耗时操作时,传统做法会阻塞主线程,导致界面无响应。

#### ❌ 传统做法的问题

```python
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
import time
import sys

class BadExample(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("传统做法示例")
        
        # 创建中心组件和布局
        central_widget = QWidget()
        layout = QVBoxLayout()
        
        self.button = QPushButton("执行耗时任务")
        self.label = QLabel("就绪")
        
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)
        
        self.button.clicked.connect(self.blocking_task)
    
    def blocking_task(self):
        """在主线程执行,会阻塞界面"""
        self.label.setText("处理中...")
        time.sleep(5)  # 主线程被阻塞
        self.label.setText("完成")

# 运行示例
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BadExample()
    window.show()
    app.exec()
```

#### ✅ 使用 QThreadWithReturn 的解决方案

```python
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QProgressBar, QVBoxLayout, QWidget
from qthreadwithreturn import QThreadWithReturn
import time
import sys

class GoodExample(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QThreadWithReturn 示例")
        
        # 创建中心组件和布局
        central_widget = QWidget()
        layout = QVBoxLayout()
        
        self.button = QPushButton("执行耗时任务")
        self.label = QLabel("就绪")
        self.progress = QProgressBar()
        
        layout.addWidget(self.label)
        layout.addWidget(self.progress)
        layout.addWidget(self.button)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)
        
        self.button.clicked.connect(self.start_task)
    
    def start_task(self):
        """使用 QThreadWithReturn 在后台执行任务"""
        def heavy_computation():
            # 模拟耗时计算
            for i in range(100):
                time.sleep(0.05)
            return "处理完成"
        
        # 创建线程
        self.thread = QThreadWithReturn(heavy_computation)
        
        # 更新UI状态
        self.button.setEnabled(False)
        self.label.setText("后台处理中...")
        self.progress.setRange(0, 0)
        
        # 设置回调
        self.thread.add_done_callback(self.on_task_completed)
        self.thread.add_failure_callback(self.on_task_failed)
        
        # 启动线程,界面保持响应
        self.thread.start()
    
    def on_task_completed(self, result):
        """任务完成回调"""
        self.button.setEnabled(True)
        self.label.setText(result)
        self.progress.setRange(0, 1)
        self.progress.setValue(1)
    
    def on_task_failed(self, exception):
        """任务失败回调"""
        self.button.setEnabled(True)
        self.label.setText(f"处理失败: {exception}")
        self.progress.setRange(0, 1)

# 运行示例
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = GoodExample()
    window.show()
    app.exec()
```

#### 🏊‍♂️ 线程池使用示例

```python
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QProgressBar, QVBoxLayout, QWidget
from qthreadwithreturn import QThreadPoolExecutor
import time
import sys

class BatchProcessingExample(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("线程池批处理示例")
        
        # 创建中心组件和布局
        central_widget = QWidget()
        layout = QVBoxLayout()
        
        self.start_btn = QPushButton("开始批处理")
        self.progress = QProgressBar()
        self.status = QLabel("就绪")
        
        layout.addWidget(self.status)
        layout.addWidget(self.progress)
        layout.addWidget(self.start_btn)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)
        
        self.start_btn.clicked.connect(self.process_files)
    
    def process_files(self):
        file_list = ["file1.txt", "file2.txt", "file3.txt", "file4.txt"]
        
        def process_single_file(filename):
            # 模拟文件处理
            time.sleep(2)
            return f"{filename} 完成"
        
        # 创建线程池
        self.pool = QThreadPoolExecutor(max_workers=2)
        self.completed_count = 0
        self.total_files = len(file_list)
        
        # 更新UI
        self.progress.setMaximum(self.total_files)
        self.progress.setValue(0)
        self.status.setText("处理中...")
        self.start_btn.setEnabled(False)
        
        # 提交任务
        for filename in file_list:
            future = self.pool.submit(process_single_file, filename)
            future.add_done_callback(self.on_file_completed)
    
    def on_file_completed(self, result):
        """任务完成回调"""
        self.completed_count += 1
        self.progress.setValue(self.completed_count)
        self.status.setText(f"完成 {self.completed_count}/{self.total_files}: {result}")
        
        if self.completed_count == self.total_files:
            self.status.setText("所有任务完成")
            self.start_btn.setEnabled(True)
            self.pool.shutdown()

# 运行示例
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BatchProcessingExample()
    window.show()
    app.exec()
```

## 🆚 与传统 QThread 对比

### 传统 QThread 实现

```python
from PySide6.QtCore import QThread, QObject, Signal

# 传统方式需要较多样板代码
class Worker(QObject):
    finished = Signal(object)
    error = Signal(Exception)
    
    def __init__(self, func, *args, **kwargs):
        super().__init__()
        self.func = func
        self.args = args
        self.kwargs = kwargs
    
    def run(self):
        try:
            result = self.func(*self.args, **self.kwargs)
            self.finished.emit(result)
        except Exception as e:
            self.error.emit(e)

# 使用时的设置
def traditional_approach():
    thread = QThread()
    worker = Worker(my_function, arg1, arg2)
    worker.moveToThread(thread)
    
    # 信号连接
    thread.started.connect(worker.run)
    worker.finished.connect(lambda result: print(f"结果: {result}"))
    worker.finished.connect(thread.quit)
    worker.finished.connect(worker.deleteLater)
    thread.finished.connect(thread.deleteLater)
    
    thread.start()
    # 获取返回值需要通过信号处理
```

### QThreadWithReturn 实现

```python
# 简化的使用方式
thread = QThreadWithReturn(my_function, arg1, arg2)
thread.add_done_callback(lambda result: print(f"结果: {result}"))
thread.start()

# 直接获取返回值
result = thread.result()
```

### 对比总结

| 特性 | 传统 QThread | QThreadWithReturn |
|------|-------------|------------------|
| **代码量** | 较多样板代码 | 简化的接口 |
| **返回值** | 信号传递 | 直接 `result()` 获取 |
| **错误处理** | 手动信号连接 | 自动异常传播 |
| **资源清理** | 手动管理 | 自动清理 |
| **超时控制** | 需额外实现 | 内置支持 |
| **任务取消** | 需自行处理 | 内置 `cancel()` |
| **线程池** | 需自己实现 | 提供现成实现 |
| **学习成本** | 需理解信号槽 | 接近标准库 API |

## 📚 高级功能

### 🎨 回调机制

```python
# 无参数回调
thread.add_done_callback(lambda: print("任务完成"))

# 单参数回调
thread.add_done_callback(lambda result: print(f"结果: {result}"))

# 多参数回调 - 自动解包
def multi_return_task():
    return 1, 2, 3

thread = QThreadWithReturn(multi_return_task)
thread.add_done_callback(lambda a, b, c: print(f"{a}, {b}, {c}"))

# 类方法回调
class ResultHandler:
    def handle_result(self, result):
        self.result = result

handler = ResultHandler()
thread.add_done_callback(handler.handle_result)
```

### ⏰ 超时控制

```python
# 设置5秒超时
thread.start(timeout_ms=5000)

try:
    result = thread.result(timeout=5.0)
except TimeoutError:
    print("任务超时")
except Exception as e:
    print(f"任务失败: {e}")
```

### 🛑 任务取消

```python
# 优雅取消
success = thread.cancel()

# 强制终止(需谨慎使用)
success = thread.cancel(force_stop=True)

# 检查状态
if thread.cancelled():
    print("任务已取消")
```

### 🔄 错误处理

```python
def failing_task():
    raise ValueError("模拟错误")

thread = QThreadWithReturn(failing_task)

# 添加失败回调
thread.add_failure_callback(lambda exc: print(f"任务失败: {exc}"))

thread.start()

try:
    result = thread.result()
except ValueError as e:
    print(f"捕获异常: {e}")
```

### 🏊‍♂️ 线程池高级用法

```python
def init_worker(worker_name):
    """工作线程初始化"""
    print(f"初始化工作线程: {worker_name}")

def compute_task(x):
    return x ** 2

with QThreadPoolExecutor(
    max_workers=4,
    thread_name_prefix="计算线程",
    initializer=init_worker,
    initargs=("数据处理器",)
) as pool:
    # 提交任务并添加回调
    future = pool.submit(compute_task, 10)
    future.add_done_callback(lambda result: print(f"计算完成: {result}"))
    
    # 等待结果
    print(future.result())  # 输出: 100
```

## 🎮 演示程序

### 🆚 GUI 对比演示
运行对比程序,体验不同实现方式的差异:

```bash
# 对比演示
python examples/gui_demo_comparison.py
```

演示内容:
- 传统做法:界面阻塞演示
- QThreadWithReturn:响应式界面演示
- 线程池:并行任务处理演示

### 📱 完整功能演示
```bash
# 完整演示程序
python -m demo.thread_demo_gui
```

### 💻 命令行示例
```bash
# 基本用法示例
python examples/basic_usage.py
```

## 🎯 应用场景

QThreadWithReturn 适合以下 GUI 应用场景:

### 📊 数据处理应用
```python
# 数据分析、文件处理
thread = QThreadWithReturn(pandas.read_csv, "large_file.csv")
thread.add_done_callback(lambda df: self.update_table_view(df))
```

### 🌐 网络应用
```python  
# HTTP请求、API调用
thread = QThreadWithReturn(requests.get, "https://api.example.com/data")
thread.add_done_callback(lambda resp: self.display_data(resp.json()))
```

### 🎨 图像处理工具
```python
# 图像处理、批量转换
with QThreadPoolExecutor(max_workers=4) as pool:
    futures = [pool.submit(process_image, img) for img in images]
    for future in pool.as_completed(futures):
        self.update_progress()
```

### 📁 文件管理器
```python
# 文件操作、批量处理
thread = QThreadWithReturn(shutil.copy2, source, destination)  
thread.add_done_callback(lambda: self.refresh_file_list())
```

### 🤖 机器学习工具
```python
# 模型推理、数据处理
thread = QThreadWithReturn(model.predict, input_data)
thread.add_done_callback(lambda result: self.show_predictions(result))
```

## 🔧 兼容性

- **Python**: 3.10+
- **Qt 版本**: PySide6 6.4+  
- **操作系统**: Windows, macOS, Linux
- **运行环境**: 
  - 有 Qt 应用环境:使用 Qt 信号机制
  - 无 Qt 应用环境:自动切换到标准线程模式
  - 已在 Python 3.10、3.11、3.13 中测试

## 🧪 测试

项目包含 73 个测试用例:

```bash
# 运行所有测试
pytest tests/

# 运行特定测试
pytest tests/test_thread_utils.py -v

# 生成覆盖率报告
pytest tests/ --cov=qthreadwithreturn
```

## 📖 API 参考

### QThreadWithReturn

| 方法 | 描述 |
|------|------|
| `start(timeout_ms=-1)` | 启动线程,可选超时设置 |
| `result(timeout=None)` | 获取执行结果,阻塞等待 |
| `exception(timeout=None)` | 获取异常信息 |
| `cancel(force_stop=False)` | 取消线程执行 |
| `running()` | 检查是否正在运行 |
| `done()` | 检查是否已完成 |
| `cancelled()` | 检查是否已取消 |
| `add_done_callback(callback)` | 添加成功完成回调 |
| `add_failure_callback(callback)` | 添加失败回调 |

### QThreadPoolExecutor  

| 方法 | 描述 |
|------|------|
| `submit(fn, *args, **kwargs)` | 提交任务到线程池 |
| `shutdown(wait=True, cancel_futures=False, force_stop=False)` | 关闭线程池 |
| `as_completed(futures, timeout=None)` | 按完成顺序迭代 Future 对象 |

## 💡 最佳实践

### ✅ 推荐做法

```python
# 1. 使用上下文管理器自动资源清理
with QThreadPoolExecutor(max_workers=4) as pool:
    futures = [pool.submit(task, i) for i in range(10)]
    results = [f.result() for f in futures]

# 2. 在回调中更新 UI(回调在主线程执行)
def update_progress(result):
    progress_bar.setValue(result)
    
thread.add_done_callback(update_progress)

# 3. 合理设置超时时间
thread.start(timeout_ms=30000)  # 30秒超时

# 4. 异常处理
try:
    result = thread.result()
except Exception as e:
    logger.error(f"任务失败: {e}")
```

### ⚠️ 注意事项

```python
# 避免在工作线程中直接更新 UI
def bad_worker():
    label.setText("更新")  # 错误:跨线程UI更新

# 记得资源清理  
pool = QThreadPoolExecutor()
# 使用完后记得调用 shutdown()

# 谨慎使用强制终止
thread.cancel(force_stop=True)  # 可能导致资源泄漏
```

## 🤝 贡献

欢迎贡献代码,请遵循以下步骤:

1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/new-feature`)
3. 提交更改 (`git commit -m 'Add new feature'`)
4. 推送到分支 (`git push origin feature/new-feature`)
5. 开启 Pull Request

### 🛠️ 开发环境设置

```bash
# 克隆仓库
git clone https://github.com/271374667/QThreadWithReturn.git
cd QThreadWithReturn

# 使用 uv 安装依赖
uv sync

# 运行测试
uv run pytest

# 运行演示
uv run python -m demo.thread_demo_gui
```

## 📄 许可证

本项目使用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。

## 📞 支持

- **问题报告**: [GitHub Issues](https://github.com/271374667/QThreadWithReturn/issues)
- **讨论**: [GitHub Discussions](https://github.com/271374667/QThreadWithReturn/discussions)
- **邮件**: 271374667@qq.com

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "qthreadwithreturn",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "PythonImporter <271374667@qq.com>",
    "keywords": "qt, pyside6, threading, concurrent, futures, gui, pyqt, async, thread-pool, worker-thread",
    "author": null,
    "author_email": "PythonImporter <271374667@qq.com>",
    "download_url": "https://files.pythonhosted.org/packages/00/44/ff0eb48bb740e32fdfda991abea78070ccc3678833c36e72e7233896f3eb/qthreadwithreturn-1.1.1.tar.gz",
    "platform": null,
    "description": "<div align=\"center\">\r\n\r\n# QThreadWithReturn\r\n\r\n![QThreadWithReturn](https://socialify.git.ci/271374667/QThreadWithReturn/image?description=1&language=1&name=1&pattern=Plus&theme=Auto)\r\n\r\n[![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://python.org)\r\n[![PySide6](https://img.shields.io/badge/PySide6-6.4+-green.svg)](https://www.qt.io/qt-for-python)\r\n[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\r\n[![Tests](https://img.shields.io/badge/Tests-73%20passed-brightgreen.svg)](tests/)\r\n\r\n\u4e00\u4e2a\u57fa\u4e8e PySide6 \u7684\u7ebf\u7a0b\u5de5\u5177\u5e93\uff0c\u7b80\u5316 GUI \u5e94\u7528\u4e2d\u7684\u591a\u7ebf\u7a0b\u7f16\u7a0b\u3002\r\n\r\nQThreadWithReturn \u4e3a\u4f20\u7edf QThread \u63d0\u4f9b\u4e86\u66f4\u76f4\u89c2\u7684 API\uff0c\u652f\u6301\u8fd4\u56de\u503c\u548c\u56de\u8c03\u673a\u5236\uff0c\u907f\u514d\u590d\u6742\u7684\u4fe1\u53f7\u69fd\u8bbe\u7f6e\u3002\r\n\r\n</div>\r\n\r\n## \u2728 \u7279\u6027\r\n\r\n### \ud83c\udfaf QThreadWithReturn\r\n- \u652f\u6301\u83b7\u53d6\u7ebf\u7a0b\u6267\u884c\u7ed3\u679c\uff0c\u63d0\u4f9b\u7c7b\u4f3c `concurrent.futures.Future` \u7684 API\r\n- \u7075\u6d3b\u7684\u56de\u8c03\u673a\u5236\uff0c\u652f\u6301\u591a\u79cd\u56de\u8c03\u51fd\u6570\u7b7e\u540d\r\n- \u5185\u7f6e\u8d85\u65f6\u63a7\u5236\u548c\u4efb\u52a1\u53d6\u6d88\r\n- \u7ebf\u7a0b\u5b89\u5168\u7684\u72b6\u6001\u7ba1\u7406\r\n- \u4e0e Qt \u4e8b\u4ef6\u5faa\u73af\u65e0\u7f1d\u96c6\u6210\r\n\r\n### \ud83c\udfca\u200d\u2642\ufe0f QThreadPoolExecutor\r\n- \u5b8c\u5168\u517c\u5bb9 `concurrent.futures.ThreadPoolExecutor` API\r\n- \u7ebf\u7a0b\u6c60\u7ba1\u7406\u548c\u4efb\u52a1\u8c03\u5ea6\r\n- \u652f\u6301\u7ebf\u7a0b\u521d\u59cb\u5316\u5668\u548c\u547d\u540d\uff0c\u4fbf\u4e8e\u8c03\u8bd5\r\n- \u652f\u6301 `as_completed` \u7b49\u6807\u51c6\u63a5\u53e3\r\n- \u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\u652f\u6301\r\n\r\n## \ud83d\ude80 \u5b89\u88c5\r\n\r\n```bash\r\n# \u4f7f\u7528 uv\r\nuv add qthreadwithreturn\r\n\r\n# \u4f7f\u7528 pip  \r\npip install qthreadwithreturn\r\n```\r\n\r\n## \ud83d\udca1 \u4f7f\u7528\u793a\u4f8b\r\n\r\n### \u95ee\u9898\u573a\u666f\r\n\r\n\u5728 GUI \u5e94\u7528\u4e2d\u6267\u884c\u8017\u65f6\u64cd\u4f5c\u65f6\uff0c\u4f20\u7edf\u505a\u6cd5\u4f1a\u963b\u585e\u4e3b\u7ebf\u7a0b\uff0c\u5bfc\u81f4\u754c\u9762\u65e0\u54cd\u5e94\u3002\r\n\r\n#### \u274c \u4f20\u7edf\u505a\u6cd5\u7684\u95ee\u9898\r\n\r\n```python\r\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget\r\nimport time\r\nimport sys\r\n\r\nclass BadExample(QMainWindow):\r\n    def __init__(self):\r\n        super().__init__()\r\n        self.setWindowTitle(\"\u4f20\u7edf\u505a\u6cd5\u793a\u4f8b\")\r\n        \r\n        # \u521b\u5efa\u4e2d\u5fc3\u7ec4\u4ef6\u548c\u5e03\u5c40\r\n        central_widget = QWidget()\r\n        layout = QVBoxLayout()\r\n        \r\n        self.button = QPushButton(\"\u6267\u884c\u8017\u65f6\u4efb\u52a1\")\r\n        self.label = QLabel(\"\u5c31\u7eea\")\r\n        \r\n        layout.addWidget(self.label)\r\n        layout.addWidget(self.button)\r\n        central_widget.setLayout(layout)\r\n        self.setCentralWidget(central_widget)\r\n        \r\n        self.button.clicked.connect(self.blocking_task)\r\n    \r\n    def blocking_task(self):\r\n        \"\"\"\u5728\u4e3b\u7ebf\u7a0b\u6267\u884c\uff0c\u4f1a\u963b\u585e\u754c\u9762\"\"\"\r\n        self.label.setText(\"\u5904\u7406\u4e2d...\")\r\n        time.sleep(5)  # \u4e3b\u7ebf\u7a0b\u88ab\u963b\u585e\r\n        self.label.setText(\"\u5b8c\u6210\")\r\n\r\n# \u8fd0\u884c\u793a\u4f8b\r\nif __name__ == \"__main__\":\r\n    app = QApplication(sys.argv)\r\n    window = BadExample()\r\n    window.show()\r\n    app.exec()\r\n```\r\n\r\n#### \u2705 \u4f7f\u7528 QThreadWithReturn \u7684\u89e3\u51b3\u65b9\u6848\r\n\r\n```python\r\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QProgressBar, QVBoxLayout, QWidget\r\nfrom qthreadwithreturn import QThreadWithReturn\r\nimport time\r\nimport sys\r\n\r\nclass GoodExample(QMainWindow):\r\n    def __init__(self):\r\n        super().__init__()\r\n        self.setWindowTitle(\"QThreadWithReturn \u793a\u4f8b\")\r\n        \r\n        # \u521b\u5efa\u4e2d\u5fc3\u7ec4\u4ef6\u548c\u5e03\u5c40\r\n        central_widget = QWidget()\r\n        layout = QVBoxLayout()\r\n        \r\n        self.button = QPushButton(\"\u6267\u884c\u8017\u65f6\u4efb\u52a1\")\r\n        self.label = QLabel(\"\u5c31\u7eea\")\r\n        self.progress = QProgressBar()\r\n        \r\n        layout.addWidget(self.label)\r\n        layout.addWidget(self.progress)\r\n        layout.addWidget(self.button)\r\n        central_widget.setLayout(layout)\r\n        self.setCentralWidget(central_widget)\r\n        \r\n        self.button.clicked.connect(self.start_task)\r\n    \r\n    def start_task(self):\r\n        \"\"\"\u4f7f\u7528 QThreadWithReturn \u5728\u540e\u53f0\u6267\u884c\u4efb\u52a1\"\"\"\r\n        def heavy_computation():\r\n            # \u6a21\u62df\u8017\u65f6\u8ba1\u7b97\r\n            for i in range(100):\r\n                time.sleep(0.05)\r\n            return \"\u5904\u7406\u5b8c\u6210\"\r\n        \r\n        # \u521b\u5efa\u7ebf\u7a0b\r\n        self.thread = QThreadWithReturn(heavy_computation)\r\n        \r\n        # \u66f4\u65b0UI\u72b6\u6001\r\n        self.button.setEnabled(False)\r\n        self.label.setText(\"\u540e\u53f0\u5904\u7406\u4e2d...\")\r\n        self.progress.setRange(0, 0)\r\n        \r\n        # \u8bbe\u7f6e\u56de\u8c03\r\n        self.thread.add_done_callback(self.on_task_completed)\r\n        self.thread.add_failure_callback(self.on_task_failed)\r\n        \r\n        # \u542f\u52a8\u7ebf\u7a0b\uff0c\u754c\u9762\u4fdd\u6301\u54cd\u5e94\r\n        self.thread.start()\r\n    \r\n    def on_task_completed(self, result):\r\n        \"\"\"\u4efb\u52a1\u5b8c\u6210\u56de\u8c03\"\"\"\r\n        self.button.setEnabled(True)\r\n        self.label.setText(result)\r\n        self.progress.setRange(0, 1)\r\n        self.progress.setValue(1)\r\n    \r\n    def on_task_failed(self, exception):\r\n        \"\"\"\u4efb\u52a1\u5931\u8d25\u56de\u8c03\"\"\"\r\n        self.button.setEnabled(True)\r\n        self.label.setText(f\"\u5904\u7406\u5931\u8d25: {exception}\")\r\n        self.progress.setRange(0, 1)\r\n\r\n# \u8fd0\u884c\u793a\u4f8b\r\nif __name__ == \"__main__\":\r\n    app = QApplication(sys.argv)\r\n    window = GoodExample()\r\n    window.show()\r\n    app.exec()\r\n```\r\n\r\n#### \ud83c\udfca\u200d\u2642\ufe0f \u7ebf\u7a0b\u6c60\u4f7f\u7528\u793a\u4f8b\r\n\r\n```python\r\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QProgressBar, QVBoxLayout, QWidget\r\nfrom qthreadwithreturn import QThreadPoolExecutor\r\nimport time\r\nimport sys\r\n\r\nclass BatchProcessingExample(QMainWindow):\r\n    def __init__(self):\r\n        super().__init__()\r\n        self.setWindowTitle(\"\u7ebf\u7a0b\u6c60\u6279\u5904\u7406\u793a\u4f8b\")\r\n        \r\n        # \u521b\u5efa\u4e2d\u5fc3\u7ec4\u4ef6\u548c\u5e03\u5c40\r\n        central_widget = QWidget()\r\n        layout = QVBoxLayout()\r\n        \r\n        self.start_btn = QPushButton(\"\u5f00\u59cb\u6279\u5904\u7406\")\r\n        self.progress = QProgressBar()\r\n        self.status = QLabel(\"\u5c31\u7eea\")\r\n        \r\n        layout.addWidget(self.status)\r\n        layout.addWidget(self.progress)\r\n        layout.addWidget(self.start_btn)\r\n        central_widget.setLayout(layout)\r\n        self.setCentralWidget(central_widget)\r\n        \r\n        self.start_btn.clicked.connect(self.process_files)\r\n    \r\n    def process_files(self):\r\n        file_list = [\"file1.txt\", \"file2.txt\", \"file3.txt\", \"file4.txt\"]\r\n        \r\n        def process_single_file(filename):\r\n            # \u6a21\u62df\u6587\u4ef6\u5904\u7406\r\n            time.sleep(2)\r\n            return f\"{filename} \u5b8c\u6210\"\r\n        \r\n        # \u521b\u5efa\u7ebf\u7a0b\u6c60\r\n        self.pool = QThreadPoolExecutor(max_workers=2)\r\n        self.completed_count = 0\r\n        self.total_files = len(file_list)\r\n        \r\n        # \u66f4\u65b0UI\r\n        self.progress.setMaximum(self.total_files)\r\n        self.progress.setValue(0)\r\n        self.status.setText(\"\u5904\u7406\u4e2d...\")\r\n        self.start_btn.setEnabled(False)\r\n        \r\n        # \u63d0\u4ea4\u4efb\u52a1\r\n        for filename in file_list:\r\n            future = self.pool.submit(process_single_file, filename)\r\n            future.add_done_callback(self.on_file_completed)\r\n    \r\n    def on_file_completed(self, result):\r\n        \"\"\"\u4efb\u52a1\u5b8c\u6210\u56de\u8c03\"\"\"\r\n        self.completed_count += 1\r\n        self.progress.setValue(self.completed_count)\r\n        self.status.setText(f\"\u5b8c\u6210 {self.completed_count}/{self.total_files}: {result}\")\r\n        \r\n        if self.completed_count == self.total_files:\r\n            self.status.setText(\"\u6240\u6709\u4efb\u52a1\u5b8c\u6210\")\r\n            self.start_btn.setEnabled(True)\r\n            self.pool.shutdown()\r\n\r\n# \u8fd0\u884c\u793a\u4f8b\r\nif __name__ == \"__main__\":\r\n    app = QApplication(sys.argv)\r\n    window = BatchProcessingExample()\r\n    window.show()\r\n    app.exec()\r\n```\r\n\r\n## \ud83c\udd9a \u4e0e\u4f20\u7edf QThread \u5bf9\u6bd4\r\n\r\n### \u4f20\u7edf QThread \u5b9e\u73b0\r\n\r\n```python\r\nfrom PySide6.QtCore import QThread, QObject, Signal\r\n\r\n# \u4f20\u7edf\u65b9\u5f0f\u9700\u8981\u8f83\u591a\u6837\u677f\u4ee3\u7801\r\nclass Worker(QObject):\r\n    finished = Signal(object)\r\n    error = Signal(Exception)\r\n    \r\n    def __init__(self, func, *args, **kwargs):\r\n        super().__init__()\r\n        self.func = func\r\n        self.args = args\r\n        self.kwargs = kwargs\r\n    \r\n    def run(self):\r\n        try:\r\n            result = self.func(*self.args, **self.kwargs)\r\n            self.finished.emit(result)\r\n        except Exception as e:\r\n            self.error.emit(e)\r\n\r\n# \u4f7f\u7528\u65f6\u7684\u8bbe\u7f6e\r\ndef traditional_approach():\r\n    thread = QThread()\r\n    worker = Worker(my_function, arg1, arg2)\r\n    worker.moveToThread(thread)\r\n    \r\n    # \u4fe1\u53f7\u8fde\u63a5\r\n    thread.started.connect(worker.run)\r\n    worker.finished.connect(lambda result: print(f\"\u7ed3\u679c: {result}\"))\r\n    worker.finished.connect(thread.quit)\r\n    worker.finished.connect(worker.deleteLater)\r\n    thread.finished.connect(thread.deleteLater)\r\n    \r\n    thread.start()\r\n    # \u83b7\u53d6\u8fd4\u56de\u503c\u9700\u8981\u901a\u8fc7\u4fe1\u53f7\u5904\u7406\r\n```\r\n\r\n### QThreadWithReturn \u5b9e\u73b0\r\n\r\n```python\r\n# \u7b80\u5316\u7684\u4f7f\u7528\u65b9\u5f0f\r\nthread = QThreadWithReturn(my_function, arg1, arg2)\r\nthread.add_done_callback(lambda result: print(f\"\u7ed3\u679c: {result}\"))\r\nthread.start()\r\n\r\n# \u76f4\u63a5\u83b7\u53d6\u8fd4\u56de\u503c\r\nresult = thread.result()\r\n```\r\n\r\n### \u5bf9\u6bd4\u603b\u7ed3\r\n\r\n| \u7279\u6027 | \u4f20\u7edf QThread | QThreadWithReturn |\r\n|------|-------------|------------------|\r\n| **\u4ee3\u7801\u91cf** | \u8f83\u591a\u6837\u677f\u4ee3\u7801 | \u7b80\u5316\u7684\u63a5\u53e3 |\r\n| **\u8fd4\u56de\u503c** | \u4fe1\u53f7\u4f20\u9012 | \u76f4\u63a5 `result()` \u83b7\u53d6 |\r\n| **\u9519\u8bef\u5904\u7406** | \u624b\u52a8\u4fe1\u53f7\u8fde\u63a5 | \u81ea\u52a8\u5f02\u5e38\u4f20\u64ad |\r\n| **\u8d44\u6e90\u6e05\u7406** | \u624b\u52a8\u7ba1\u7406 | \u81ea\u52a8\u6e05\u7406 |\r\n| **\u8d85\u65f6\u63a7\u5236** | \u9700\u989d\u5916\u5b9e\u73b0 | \u5185\u7f6e\u652f\u6301 |\r\n| **\u4efb\u52a1\u53d6\u6d88** | \u9700\u81ea\u884c\u5904\u7406 | \u5185\u7f6e `cancel()` |\r\n| **\u7ebf\u7a0b\u6c60** | \u9700\u81ea\u5df1\u5b9e\u73b0 | \u63d0\u4f9b\u73b0\u6210\u5b9e\u73b0 |\r\n| **\u5b66\u4e60\u6210\u672c** | \u9700\u7406\u89e3\u4fe1\u53f7\u69fd | \u63a5\u8fd1\u6807\u51c6\u5e93 API |\r\n\r\n## \ud83d\udcda \u9ad8\u7ea7\u529f\u80fd\r\n\r\n### \ud83c\udfa8 \u56de\u8c03\u673a\u5236\r\n\r\n```python\r\n# \u65e0\u53c2\u6570\u56de\u8c03\r\nthread.add_done_callback(lambda: print(\"\u4efb\u52a1\u5b8c\u6210\"))\r\n\r\n# \u5355\u53c2\u6570\u56de\u8c03\r\nthread.add_done_callback(lambda result: print(f\"\u7ed3\u679c: {result}\"))\r\n\r\n# \u591a\u53c2\u6570\u56de\u8c03 - \u81ea\u52a8\u89e3\u5305\r\ndef multi_return_task():\r\n    return 1, 2, 3\r\n\r\nthread = QThreadWithReturn(multi_return_task)\r\nthread.add_done_callback(lambda a, b, c: print(f\"{a}, {b}, {c}\"))\r\n\r\n# \u7c7b\u65b9\u6cd5\u56de\u8c03\r\nclass ResultHandler:\r\n    def handle_result(self, result):\r\n        self.result = result\r\n\r\nhandler = ResultHandler()\r\nthread.add_done_callback(handler.handle_result)\r\n```\r\n\r\n### \u23f0 \u8d85\u65f6\u63a7\u5236\r\n\r\n```python\r\n# \u8bbe\u7f6e5\u79d2\u8d85\u65f6\r\nthread.start(timeout_ms=5000)\r\n\r\ntry:\r\n    result = thread.result(timeout=5.0)\r\nexcept TimeoutError:\r\n    print(\"\u4efb\u52a1\u8d85\u65f6\")\r\nexcept Exception as e:\r\n    print(f\"\u4efb\u52a1\u5931\u8d25: {e}\")\r\n```\r\n\r\n### \ud83d\uded1 \u4efb\u52a1\u53d6\u6d88\r\n\r\n```python\r\n# \u4f18\u96c5\u53d6\u6d88\r\nsuccess = thread.cancel()\r\n\r\n# \u5f3a\u5236\u7ec8\u6b62\uff08\u9700\u8c28\u614e\u4f7f\u7528\uff09\r\nsuccess = thread.cancel(force_stop=True)\r\n\r\n# \u68c0\u67e5\u72b6\u6001\r\nif thread.cancelled():\r\n    print(\"\u4efb\u52a1\u5df2\u53d6\u6d88\")\r\n```\r\n\r\n### \ud83d\udd04 \u9519\u8bef\u5904\u7406\r\n\r\n```python\r\ndef failing_task():\r\n    raise ValueError(\"\u6a21\u62df\u9519\u8bef\")\r\n\r\nthread = QThreadWithReturn(failing_task)\r\n\r\n# \u6dfb\u52a0\u5931\u8d25\u56de\u8c03\r\nthread.add_failure_callback(lambda exc: print(f\"\u4efb\u52a1\u5931\u8d25: {exc}\"))\r\n\r\nthread.start()\r\n\r\ntry:\r\n    result = thread.result()\r\nexcept ValueError as e:\r\n    print(f\"\u6355\u83b7\u5f02\u5e38: {e}\")\r\n```\r\n\r\n### \ud83c\udfca\u200d\u2642\ufe0f \u7ebf\u7a0b\u6c60\u9ad8\u7ea7\u7528\u6cd5\r\n\r\n```python\r\ndef init_worker(worker_name):\r\n    \"\"\"\u5de5\u4f5c\u7ebf\u7a0b\u521d\u59cb\u5316\"\"\"\r\n    print(f\"\u521d\u59cb\u5316\u5de5\u4f5c\u7ebf\u7a0b: {worker_name}\")\r\n\r\ndef compute_task(x):\r\n    return x ** 2\r\n\r\nwith QThreadPoolExecutor(\r\n    max_workers=4,\r\n    thread_name_prefix=\"\u8ba1\u7b97\u7ebf\u7a0b\",\r\n    initializer=init_worker,\r\n    initargs=(\"\u6570\u636e\u5904\u7406\u5668\",)\r\n) as pool:\r\n    # \u63d0\u4ea4\u4efb\u52a1\u5e76\u6dfb\u52a0\u56de\u8c03\r\n    future = pool.submit(compute_task, 10)\r\n    future.add_done_callback(lambda result: print(f\"\u8ba1\u7b97\u5b8c\u6210: {result}\"))\r\n    \r\n    # \u7b49\u5f85\u7ed3\u679c\r\n    print(future.result())  # \u8f93\u51fa: 100\r\n```\r\n\r\n## \ud83c\udfae \u6f14\u793a\u7a0b\u5e8f\r\n\r\n### \ud83c\udd9a GUI \u5bf9\u6bd4\u6f14\u793a\r\n\u8fd0\u884c\u5bf9\u6bd4\u7a0b\u5e8f\uff0c\u4f53\u9a8c\u4e0d\u540c\u5b9e\u73b0\u65b9\u5f0f\u7684\u5dee\u5f02\uff1a\r\n\r\n```bash\r\n# \u5bf9\u6bd4\u6f14\u793a\r\npython examples/gui_demo_comparison.py\r\n```\r\n\r\n\u6f14\u793a\u5185\u5bb9\uff1a\r\n- \u4f20\u7edf\u505a\u6cd5\uff1a\u754c\u9762\u963b\u585e\u6f14\u793a\r\n- QThreadWithReturn\uff1a\u54cd\u5e94\u5f0f\u754c\u9762\u6f14\u793a\r\n- \u7ebf\u7a0b\u6c60\uff1a\u5e76\u884c\u4efb\u52a1\u5904\u7406\u6f14\u793a\r\n\r\n### \ud83d\udcf1 \u5b8c\u6574\u529f\u80fd\u6f14\u793a\r\n```bash\r\n# \u5b8c\u6574\u6f14\u793a\u7a0b\u5e8f\r\npython -m demo.thread_demo_gui\r\n```\r\n\r\n### \ud83d\udcbb \u547d\u4ee4\u884c\u793a\u4f8b\r\n```bash\r\n# \u57fa\u672c\u7528\u6cd5\u793a\u4f8b\r\npython examples/basic_usage.py\r\n```\r\n\r\n## \ud83c\udfaf \u5e94\u7528\u573a\u666f\r\n\r\nQThreadWithReturn \u9002\u5408\u4ee5\u4e0b GUI \u5e94\u7528\u573a\u666f\uff1a\r\n\r\n### \ud83d\udcca \u6570\u636e\u5904\u7406\u5e94\u7528\r\n```python\r\n# \u6570\u636e\u5206\u6790\u3001\u6587\u4ef6\u5904\u7406\r\nthread = QThreadWithReturn(pandas.read_csv, \"large_file.csv\")\r\nthread.add_done_callback(lambda df: self.update_table_view(df))\r\n```\r\n\r\n### \ud83c\udf10 \u7f51\u7edc\u5e94\u7528\r\n```python  \r\n# HTTP\u8bf7\u6c42\u3001API\u8c03\u7528\r\nthread = QThreadWithReturn(requests.get, \"https://api.example.com/data\")\r\nthread.add_done_callback(lambda resp: self.display_data(resp.json()))\r\n```\r\n\r\n### \ud83c\udfa8 \u56fe\u50cf\u5904\u7406\u5de5\u5177\r\n```python\r\n# \u56fe\u50cf\u5904\u7406\u3001\u6279\u91cf\u8f6c\u6362\r\nwith QThreadPoolExecutor(max_workers=4) as pool:\r\n    futures = [pool.submit(process_image, img) for img in images]\r\n    for future in pool.as_completed(futures):\r\n        self.update_progress()\r\n```\r\n\r\n### \ud83d\udcc1 \u6587\u4ef6\u7ba1\u7406\u5668\r\n```python\r\n# \u6587\u4ef6\u64cd\u4f5c\u3001\u6279\u91cf\u5904\u7406\r\nthread = QThreadWithReturn(shutil.copy2, source, destination)  \r\nthread.add_done_callback(lambda: self.refresh_file_list())\r\n```\r\n\r\n### \ud83e\udd16 \u673a\u5668\u5b66\u4e60\u5de5\u5177\r\n```python\r\n# \u6a21\u578b\u63a8\u7406\u3001\u6570\u636e\u5904\u7406\r\nthread = QThreadWithReturn(model.predict, input_data)\r\nthread.add_done_callback(lambda result: self.show_predictions(result))\r\n```\r\n\r\n## \ud83d\udd27 \u517c\u5bb9\u6027\r\n\r\n- **Python**: 3.10+\r\n- **Qt \u7248\u672c**: PySide6 6.4+  \r\n- **\u64cd\u4f5c\u7cfb\u7edf**: Windows, macOS, Linux\r\n- **\u8fd0\u884c\u73af\u5883**: \r\n  - \u6709 Qt \u5e94\u7528\u73af\u5883\uff1a\u4f7f\u7528 Qt \u4fe1\u53f7\u673a\u5236\r\n  - \u65e0 Qt \u5e94\u7528\u73af\u5883\uff1a\u81ea\u52a8\u5207\u6362\u5230\u6807\u51c6\u7ebf\u7a0b\u6a21\u5f0f\r\n  - \u5df2\u5728 Python 3.10\u30013.11\u30013.13 \u4e2d\u6d4b\u8bd5\r\n\r\n## \ud83e\uddea \u6d4b\u8bd5\r\n\r\n\u9879\u76ee\u5305\u542b 73 \u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff1a\r\n\r\n```bash\r\n# \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\r\npytest tests/\r\n\r\n# \u8fd0\u884c\u7279\u5b9a\u6d4b\u8bd5\r\npytest tests/test_thread_utils.py -v\r\n\r\n# \u751f\u6210\u8986\u76d6\u7387\u62a5\u544a\r\npytest tests/ --cov=qthreadwithreturn\r\n```\r\n\r\n## \ud83d\udcd6 API \u53c2\u8003\r\n\r\n### QThreadWithReturn\r\n\r\n| \u65b9\u6cd5 | \u63cf\u8ff0 |\r\n|------|------|\r\n| `start(timeout_ms=-1)` | \u542f\u52a8\u7ebf\u7a0b\uff0c\u53ef\u9009\u8d85\u65f6\u8bbe\u7f6e |\r\n| `result(timeout=None)` | \u83b7\u53d6\u6267\u884c\u7ed3\u679c\uff0c\u963b\u585e\u7b49\u5f85 |\r\n| `exception(timeout=None)` | \u83b7\u53d6\u5f02\u5e38\u4fe1\u606f |\r\n| `cancel(force_stop=False)` | \u53d6\u6d88\u7ebf\u7a0b\u6267\u884c |\r\n| `running()` | \u68c0\u67e5\u662f\u5426\u6b63\u5728\u8fd0\u884c |\r\n| `done()` | \u68c0\u67e5\u662f\u5426\u5df2\u5b8c\u6210 |\r\n| `cancelled()` | \u68c0\u67e5\u662f\u5426\u5df2\u53d6\u6d88 |\r\n| `add_done_callback(callback)` | \u6dfb\u52a0\u6210\u529f\u5b8c\u6210\u56de\u8c03 |\r\n| `add_failure_callback(callback)` | \u6dfb\u52a0\u5931\u8d25\u56de\u8c03 |\r\n\r\n### QThreadPoolExecutor  \r\n\r\n| \u65b9\u6cd5 | \u63cf\u8ff0 |\r\n|------|------|\r\n| `submit(fn, *args, **kwargs)` | \u63d0\u4ea4\u4efb\u52a1\u5230\u7ebf\u7a0b\u6c60 |\r\n| `shutdown(wait=True, cancel_futures=False, force_stop=False)` | \u5173\u95ed\u7ebf\u7a0b\u6c60 |\r\n| `as_completed(futures, timeout=None)` | \u6309\u5b8c\u6210\u987a\u5e8f\u8fed\u4ee3 Future \u5bf9\u8c61 |\r\n\r\n## \ud83d\udca1 \u6700\u4f73\u5b9e\u8df5\r\n\r\n### \u2705 \u63a8\u8350\u505a\u6cd5\r\n\r\n```python\r\n# 1. \u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\u81ea\u52a8\u8d44\u6e90\u6e05\u7406\r\nwith QThreadPoolExecutor(max_workers=4) as pool:\r\n    futures = [pool.submit(task, i) for i in range(10)]\r\n    results = [f.result() for f in futures]\r\n\r\n# 2. \u5728\u56de\u8c03\u4e2d\u66f4\u65b0 UI\uff08\u56de\u8c03\u5728\u4e3b\u7ebf\u7a0b\u6267\u884c\uff09\r\ndef update_progress(result):\r\n    progress_bar.setValue(result)\r\n    \r\nthread.add_done_callback(update_progress)\r\n\r\n# 3. \u5408\u7406\u8bbe\u7f6e\u8d85\u65f6\u65f6\u95f4\r\nthread.start(timeout_ms=30000)  # 30\u79d2\u8d85\u65f6\r\n\r\n# 4. \u5f02\u5e38\u5904\u7406\r\ntry:\r\n    result = thread.result()\r\nexcept Exception as e:\r\n    logger.error(f\"\u4efb\u52a1\u5931\u8d25: {e}\")\r\n```\r\n\r\n### \u26a0\ufe0f \u6ce8\u610f\u4e8b\u9879\r\n\r\n```python\r\n# \u907f\u514d\u5728\u5de5\u4f5c\u7ebf\u7a0b\u4e2d\u76f4\u63a5\u66f4\u65b0 UI\r\ndef bad_worker():\r\n    label.setText(\"\u66f4\u65b0\")  # \u9519\u8bef\uff1a\u8de8\u7ebf\u7a0bUI\u66f4\u65b0\r\n\r\n# \u8bb0\u5f97\u8d44\u6e90\u6e05\u7406  \r\npool = QThreadPoolExecutor()\r\n# \u4f7f\u7528\u5b8c\u540e\u8bb0\u5f97\u8c03\u7528 shutdown()\r\n\r\n# \u8c28\u614e\u4f7f\u7528\u5f3a\u5236\u7ec8\u6b62\r\nthread.cancel(force_stop=True)  # \u53ef\u80fd\u5bfc\u81f4\u8d44\u6e90\u6cc4\u6f0f\r\n```\r\n\r\n## \ud83e\udd1d \u8d21\u732e\r\n\r\n\u6b22\u8fce\u8d21\u732e\u4ee3\u7801\uff0c\u8bf7\u9075\u5faa\u4ee5\u4e0b\u6b65\u9aa4\uff1a\r\n\r\n1. Fork \u672c\u4ed3\u5e93\r\n2. \u521b\u5efa\u7279\u6027\u5206\u652f (`git checkout -b feature/new-feature`)\r\n3. \u63d0\u4ea4\u66f4\u6539 (`git commit -m 'Add new feature'`)\r\n4. \u63a8\u9001\u5230\u5206\u652f (`git push origin feature/new-feature`)\r\n5. \u5f00\u542f Pull Request\r\n\r\n### \ud83d\udee0\ufe0f \u5f00\u53d1\u73af\u5883\u8bbe\u7f6e\r\n\r\n```bash\r\n# \u514b\u9686\u4ed3\u5e93\r\ngit clone https://github.com/271374667/QThreadWithReturn.git\r\ncd QThreadWithReturn\r\n\r\n# \u4f7f\u7528 uv \u5b89\u88c5\u4f9d\u8d56\r\nuv sync\r\n\r\n# \u8fd0\u884c\u6d4b\u8bd5\r\nuv run pytest\r\n\r\n# \u8fd0\u884c\u6f14\u793a\r\nuv run python -m demo.thread_demo_gui\r\n```\r\n\r\n## \ud83d\udcc4 \u8bb8\u53ef\u8bc1\r\n\r\n\u672c\u9879\u76ee\u4f7f\u7528 MIT \u8bb8\u53ef\u8bc1 - \u67e5\u770b [LICENSE](LICENSE) \u6587\u4ef6\u4e86\u89e3\u8be6\u60c5\u3002\r\n\r\n## \ud83d\udcde \u652f\u6301\r\n\r\n- **\u95ee\u9898\u62a5\u544a**: [GitHub Issues](https://github.com/271374667/QThreadWithReturn/issues)\r\n- **\u8ba8\u8bba**: [GitHub Discussions](https://github.com/271374667/QThreadWithReturn/discussions)\r\n- **\u90ae\u4ef6**: 271374667@qq.com\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "PySide6 \u9ad8\u7ea7\u7ebf\u7a0b\u5de5\u5177\u5e93 - \u5e26\u8fd4\u56de\u503c\u7684\u7ebf\u7a0b\u7c7b\u548c\u7ebf\u7a0b\u6c60\u6267\u884c\u5668",
    "version": "1.1.1",
    "project_urls": {
        "Homepage": "https://github.com/271374667/QThreadWithReturn",
        "Issues": "https://github.com/271374667/QThreadWithReturn/issues",
        "Repository": "https://github.com/271374667/QThreadWithReturn.git"
    },
    "split_keywords": [
        "qt",
        " pyside6",
        " threading",
        " concurrent",
        " futures",
        " gui",
        " pyqt",
        " async",
        " thread-pool",
        " worker-thread"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "fde8fd123004c7d454c7dc04a89dcaebdfe40ea97441587e1efeecf943598a1f",
                "md5": "bebe71e839d03685d6fe0247434634e6",
                "sha256": "5b7f8a237ee4349105df8958a7e48c3ced678c6316f393a4d133bf67e11247c3"
            },
            "downloads": -1,
            "filename": "qthreadwithreturn-1.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "bebe71e839d03685d6fe0247434634e6",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 18218,
            "upload_time": "2025-08-12T08:43:03",
            "upload_time_iso_8601": "2025-08-12T08:43:03.174268Z",
            "url": "https://files.pythonhosted.org/packages/fd/e8/fd123004c7d454c7dc04a89dcaebdfe40ea97441587e1efeecf943598a1f/qthreadwithreturn-1.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0044ff0eb48bb740e32fdfda991abea78070ccc3678833c36e72e7233896f3eb",
                "md5": "276761a884105623f5815b1801ad3b74",
                "sha256": "fe8a43c553bade91f6d09fcdcd52165fd9c14b79b8e74b3f289fb212f8bcf9bd"
            },
            "downloads": -1,
            "filename": "qthreadwithreturn-1.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "276761a884105623f5815b1801ad3b74",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 41575,
            "upload_time": "2025-08-12T08:43:04",
            "upload_time_iso_8601": "2025-08-12T08:43:04.399287Z",
            "url": "https://files.pythonhosted.org/packages/00/44/ff0eb48bb740e32fdfda991abea78070ccc3678833c36e72e7233896f3eb/qthreadwithreturn-1.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-12 08:43:04",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "271374667",
    "github_project": "QThreadWithReturn",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "qthreadwithreturn"
}
        
Elapsed time: 2.27891s