python-3.x - 如何在 PyQt5 应用程序中发出 Web 请求而不挂起 UI?
问题描述
我有一个在 Python 3.7 中运行的 PyQt5 应用程序,它发出很多 Web 请求,但一次不超过一两个。大多数请求很快,但有时需要几秒钟。整个 UI 挂起,直到请求完成。我在下面有一个完整的示例应用程序来演示这一点。
环顾四周,有些人建议使用QThread
,但如果我尝试这样做,我会抱怨从错误的线程访问 UI 对象。该方法还阻止我将 Web 响应返回给调用者。在下面的示例代码中,响应仅用于打印状态码,但在实际应用中,会解析并使用 JSON 响应。我发现这个问题的大多数答案都超过五年了,通常引用 PyQt4 或更早版本,或者引用现在已经过时的包,例如grequests或request-threads。有些人引用httpx(仍处于测试阶段)或aiohttp,它们有自己的事件循环,并且在 PyQt5 应用程序中并不总是很好。
样本被尽可能地精简以证明问题。如果您单击“发出请求”并立即单击“单击计数器”,您将看到计数器在 Web 请求完成之前不会增加。这并不奇怪,因为请求阻塞了 Qt 事件循环。
我怎样才能简单地让当前代码在请求被提交和异步管理的地方工作,但仍然能够将响应传递回调用者?
这里是test.py
:
import sys
from datetime import datetime
import requests
from PyQt5 import QtWidgets, uic
from requests import RequestException
class UI(QtWidgets.QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi('test.ui', self)
self.web = WebManager(self)
self.issue_request_button.clicked.connect(self.issue_request)
self.issue_click_button.clicked.connect(self.click_counter)
self.show()
def click_counter(self):
self.click_count_label.setText(str(int(self.click_count_label.text()) + 1))
def issue_request(self):
r = self.web.issue_get(self.request_url.text())
print(r.status_code if r is not None else 'No successful response')
class WebManager:
def __init__(self, gui):
self.gui = gui
def issue_get(self, url_request):
try:
start = datetime.now()
headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
r = requests.get(url_request, headers=headers)
self.fill_request_response_info(r, datetime.now() - start)
return r
except RequestException as e:
print(f'Request failed with an Exception of type {type(e).__name__}')
return None
def fill_request_response_info(self, response, total_request_time):
request = response.request
self.gui.request_url_textbox.setText(request.method + ' ' + request.url)
self.gui.request_headers_textbox.setText('\n'.join(k + ':' + v for k, v in request.headers.items()))
self.gui.response_status_textbox.setText(f'{response.status_code} {response.reason}')
self.gui.response_elapsed_time_textbox.setText(f'{response.elapsed} / {total_request_time}')
self.gui.response_headers_textbox.setText('\n'.join(k + ':' + v for k, v in response.headers.items()))
self.gui.response_body_textbox.setText(response.content.decode('utf-8'))
app = QtWidgets.QApplication(sys.argv)
window = UI()
app.exec()
这是它需要的 UI 文件test.ui
:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>750</width>
<height>734</height>
</rect>
</property>
<property name="windowTitle">
<string>Test Window</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="issue_request_button">
<property name="text">
<string>Make Request</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>URL:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="request_url">
<property name="text">
<string>https://archive.org/advancedsearch.php?q=subject:google+sheets&output=json</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="issue_click_button">
<property name="text">
<string>Click Counter</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="click_count_label">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="http_request_groupBox">
<property name="title">
<string>HTTP Request</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="req_url_label">
<property name="text">
<string>Request URL:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="request_url_textbox"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="req_headers_label">
<property name="text">
<string>Request Headers:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="request_headers_textbox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="http_response_groupBox">
<property name="title">
<string>HTTP Response</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,1,0,1">
<item>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0">
<item row="0" column="0">
<widget class="QLabel" name="http_status_label">
<property name="text">
<string>HTTP Status:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="elapsed_time_label">
<property name="text">
<string>Elapsed Time:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="response_status_textbox"/>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="response_elapsed_time_textbox"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="response_headers_label">
<property name="text">
<string>Response Headers:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="response_headers_textbox"/>
</item>
<item>
<widget class="QLabel" name="response_body_label">
<property name="text">
<string>Response Body:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="response_body_textbox"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>issue_request_button</tabstop>
<tabstop>request_url</tabstop>
<tabstop>issue_click_button</tabstop>
<tabstop>request_url_textbox</tabstop>
<tabstop>request_headers_textbox</tabstop>
<tabstop>response_status_textbox</tabstop>
<tabstop>response_elapsed_time_textbox</tabstop>
<tabstop>response_headers_textbox</tabstop>
<tabstop>response_body_textbox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
解决方案
推荐阅读
- azure-web-app-service - Azure 上的 SignalR 长时间断开连接超时
- angular - 三级域上的角度直接链接错误
- html - 羽毛。如何将初始页面重定向到视图文件夹而不是公用文件夹中的页面
- angular - 具有多个动作角度的效果
- responsive-design - 如何在 Tailwind 中将元素设置为显示在中屏及下方?
- ios - 我们如何在 Swift(iOS) 中的 AppleWatch 扩展中进行水平滚动
- powerbi - 任何人都知道为什么最近与 SharePoint 在线列表的连接停止了?
- android - 传感器总是在一秒钟内给我 100 个事件值
- dataframe - 从数据框的行中删除仅包含数字的字符串
- python - 没有权限。joblib 将以串行模式运行