问题引入
如下代码在qml主线程进行listmodel大量数据更新操作导致qml主线程界面UI卡顿
bash
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Item{
id:itemRoot
anchors.left: parent.left
anchors.right: parent.right
height: 40
clip: true
Row{
Repeater{
model: ["property1","property2","property3", "property4", "property5"]
Rectangle{
width: itemRoot.width / 5
height: itemRoot.height
color: "cyan"
Text {
anchors.centerIn: parent
text: modelData
}
}
}
}
}
Timer{
interval: 1
running: true
repeat: true
onTriggered: {
for(let i = 0; i < listmode.count; i++)
{
let data = listmode.get(i)
listmode.set(i, {val1:data.val1 + 0.001,val2:data.val2 + 0.002,val3:data.val3 + 0.003,val4:data.val4 + 0.004,val5:data.val5 + 0.005})
}
}
}
ListModel{
id:listmode
Component.onCompleted: {
for(let i = 0; i < 10000; i++)
{
listmode.append({val1:0.001,val2:0.002,val3:0.003,val4:0.004,val5:0.005})
}
}
}
Item{
anchors.fill: parent
anchors.topMargin: 40
clip: true
ListView{
id:listview
model:listmode
anchors.fill: parent
delegate: Rectangle{
width: itemRoot.width
height: 40
color: "white"
border.width: 1
border.color: "yellow"
Row{
Item{
width: listview.width / 5
height: 40
Text {
text: val1
anchors.centerIn: parent
}
}
Item{
width: listview.width / 5
height: 40
Text {
text: val2
anchors.centerIn: parent
}
}
Item{
width: listview.width / 5
height: 40
Text {
text: val3
anchors.centerIn: parent
}
}
Item{
width: listview.width / 5
height: 40
Text {
text: val4
anchors.centerIn: parent
}
}
Item{
width: listview.width / 5
height: 40
Text {
text: val5
anchors.centerIn: parent
}
}
}
}
}
}
}
问题分析
QML 的界面渲染、动画播放和用户交互都在同一个主线程中运行。如果你在主线程中执行复杂的 JavaScript 计算(如大量数据处理、复杂算法、文件读写),界面就会"冻结",直到计算完成。
复杂的 ListModel 操作
场景:当 ListView 需要加载成千上万条数据时,直接在主线程循环 append 会非常慢。
WorkerScript 方案:利用 Worker 处理数据模型,甚至可以使用 ListModel.sync() 方法(特定于 Qt 的扩展功能)来高效地批量更新列表数据,减少 UI 的重绘次数
解决方案
WorkerScript 通过引入多线程机制,将这些繁重的任务剥离到后台线程执行,从而保证主界面的流畅度
-
核心机制:消息传递 (Message Passing)
使用 WorkerScript 时,你必须接受一个限制:后台线程无法直接访问 QML 界面元素(如不能直接修改 textItem.text)。
它们之间的通信完全依赖消息传递:
-
主线程 -> 子线程:使用 worker.sendMessage({ data: ... }) 发送任务参数。
-
子线程处理:在 worker.js 中监听 WorkerScript.onMessage,执行耗时计算。
-
子线程 -> 主线程:计算完成后,使用 WorkerScript.sendMessage({ result: ... }) 发回结果。
-
主线程更新:在 QML 中监听 onMessage 信号,拿到结果后更新 UI
具体代码实现

调用的worker.js文件
bash
WorkerScript.onMessage = function func(msg) {
let listmodel = msg
for(let i = 0; i < listmodel.count; i++)
{
let data = listmodel.get(i)
listmodel.set(i, {val1:data.val1 + 0.001,val2:data.val2 + 0.002,val3:data.val3 + 0.003,val4:data.val4 + 0.004,val5:data.val5 + 0.005})
}
listmodel.sync() //将子线程处理后的结果返回给qml主线程,qml主线程通过onMessage接收消息
}
拓展:
如果想要
worker.sendMessage({model:listmodel, value:"123"})
那么在work.js中
c
WorkerScript.onMessage = function func(msg) {
// let listmodel = msg
let listmodel = msg.model
for(let i = 0; i < listmodel.count; i++)
{
let data = listmodel.get(i)
listmodel.set(i, {val1:data.val1 + 0.001,val2:data.val2 + 0.002,val3:data.val3 + 0.003,val4:data.val4 + 0.004,val5:data.val5 + 0.005})
}
listmodel.sync() //将子线程处理后的结果返回给qml主线程,qml主线程通过onMessage接收消息
}
总结
-
解决"界面假死"问题 (UI Freezing)
这是最直接的应用场景。
问题:当你点击一个按钮,需要处理一个包含 10,000 条数据的数组,或者进行复杂的数学运算(如斐波那契数列计算、图像处理算法)。如果直接在 QML 的 onClicked 中写循环,界面会卡住不动,用户无法点击其他按钮,动画也会停止。
WorkerScript 方案:将计算逻辑放入 worker.js 文件中。主线程发送数据给 Worker,Worker 在后台算好后,通过消息机制把结果发回主线程更新 UI。在此过程中,用户依然可以流畅地拖动窗口、点击菜单。
-
处理大数据与复杂逻辑
JSON 解析/序列化:解析巨大的 JSON 字符串(例如从服务器返回的大型报表数据)非常消耗 CPU。
数据清洗与排序:对 ListModel 中的大量条目进行复杂的筛选、排序或格式化。
加密/解密:涉及复杂的位运算和数学计算的加密算法。
-
文件 I/O 操作 (配合 LocalStorage 或 C++)
问题:虽然 QML 本身不直接支持在 Worker 中读写文件(受限于沙箱安全机制),但通过 Worker 可以避免同步 I/O 造成的阻塞。
场景:读取一个很大的日志文件,或者将大量数据写入数据库。通常的做法是在 Worker 中调用 C++ 暴露的接口,或者使用 QtQuick.LocalStorage 进行异步数据库操作,避免磁盘 I/O 等待阻塞界面渲染。
-
网络请求数据处理
虽然 QML 的 XmlHttpRequest 本身支持异步,但如果你需要在请求回来后立刻进行大量的数据转换(例如将原始数据转换为图表所需的格式),这个转换过程依然可能卡顿。
WorkerScript 方案:网络请求回来后,把原始数据丢给 Worker 进行解析和重组,主线程只负责最后展示。
-
复杂的 ListModel 操作
场景:当 ListView 需要加载成千上万条数据时,直接在主线程循环 append 会非常慢。
WorkerScript 方案:利用 Worker 处理数据模型,甚至可以使用 ListModel.sync() 方法(特定于 Qt 的扩展功能)来高效地批量更新列表数据,减少 UI 的重绘次数。
