vue项目中如何使用多线程worker
项目背景:写一个定时器计时,但是和另外一个计时器产生事件循环问题,导致计时不准,所以把计时器放到另外一个线程里,避免两个计时器及渲染冲突
1.安装worker-loader
npm install worker-loader
2. 配置webpack
在vue.config.js文件的defineConfig里加上配置参数
//多线程worker-loader配置项
chainWebpack: config => {
config.module
.rule('worker-loader')
.test(/\.worker\.js$/)
.use({
loader: 'worker-loader',
options: {
inline: true
}
})
.loader('worker-loader')
.end()
// 解决:worker 热更新问题
config.module.rule('js').exclude.add(/\.worker\.js$/);
}
3.使用
先在src目录下新建workers文件夹,接着在里面新建worker.js,在js文件里添加下面的测试代码:
addEventListener('message', e => {
const { data } = e
console.log(data)
setTimeout(() => {
return postMessage('线程完成')
}, 1000)
})
export default {}
之后就可以新建一个vue文件,加入下面代码进行测试:
引入js文件时需要使用worker-loader!@,并且路径是从src目录下开始数层级的,并非你执行的vue文件开始,这就是我一直不能正常跑起来的原因。
<script>
import Worker1 from 'worker-loader!@/workers/worker1'
export default {
data(){
return {
events :[]
}
},
mounted(){
const worker1 = new Worker1()
setInterval(()=>{
worker1.postMessage('开启线程1')
worker1.onmessage = e => {
if(e.data.result == 1){
that.events = e.data.data
}
}
},10000)
},
}
项目完整代码:
src => workers => worker.js
/**该多线程脚本用途
*在规定时间内快速走完24小时的时间
*/
/**
*
* @param {*} ste 定时器时间(计算的频率)
* @param {*} countNum 计数的步伐
* @param {*} totalTime 计时总时间,目前计时24小时的
*/
const onTimer = (ste,countNum,totalTime) => {
let count = 0 //计数器
const total = totalTime * 60 * 60
let HH = "00" //返回去的时针
let MM = "00" //返回去的分针
let SS = "00" //返回去的秒针
let timer = setInterval(() => {
if (count >= total) { // 到了24小时自动停止
if (timer) {
clearInterval(timer)
timer = null
}
SS = "00"
MM = "00"
HH = "24"
postMessage({code:0, HH ,MM ,SS })
return
}
count += countNum
let countS = count % 60
SS = formatter2(countS)
let countM = Math.floor(count / 60) % 60
MM = formatter2(countM)
let countH = Math.floor(count / 60 / 60) % 60
HH = formatter2(countH)
postMessage({code:1, HH ,MM ,SS })
return
}, ste)
}
/**
*
* @param {*} time 传入时间改成两位数返回
* @returns
*/
const formatter2=(time) => {
let time2 = time+ ""
let time3 = time2.split(".")[0]
if (time3 < 10) {
return '0' + time3
} else {
return time3
}
}
addEventListener('message', e => {
const { data } = e
if (data.code === 1) {
const {ste,countNum,totalTime} = data
onTimer(ste,countNum,totalTime)
}
})
export default {}
主线程文件
<template>
<div class="container">
<div class="digitalFlop timeBox">{{ H }}:{{ M }}:{{ S }}</div>
<div class="date">{{ year }} 年 {{ month }} 月 {{ day }} 日</div>
<div class="date opend">当前开机数</div>
<dv-digital-flop :config="configOpen" class="digitalFlop center"/>
<div class="date closed">当日开机总数</div>
<dv-digital-flop :config="configClose" class="digitalFlop bottom"/>
<div id="map"></div>
</div>
</template>
<script>
import { newgaode, getTerminal, getTime } from '@/api/table'
import { Scene, PointLayer, Popup } from '@antv/l7'
import { GaodeMap } from '@antv/l7-maps'
import Worker from 'worker-loader!@/workers/worker'
let layer = null //底层点位图实例对象
let layer2 = null //中间层点位图实例对象
let layer3 = null //顶层点位图实例对象
let layer4 = null //顶层点位图实例对象
let param = -1 //发送请求的计数器
const timeStep = 1.5 * 1000 // 每次递归请求的时间间隔,单位毫秒
const ste = 20 //左上角时间的递归频率
export default {
name: 'DayofvisionVera',
data() {
return {
array: [], //最底层图层的数组
topArray: [], //最顶层图层的数组
scene: null, //全局的场景最底层搞得地图的实例对象
year: '-', //年
month: '-', //月
day: '-', //日
configOpen: {
//展示开机数量的翻牌器
style: {
fontSize: 55,
fill: '#fff'
},
number: [0],
content: '{nt}'
},
configClose: {
//展示关机数量的翻牌器
style: {
fontSize: 55,
fill: '#fff'
},
number: [0],
content: '{nt}'
},
paramData : this.getParam(15),//切割后的参数
timerOne: null,//递归函数里的定时器
timeLater:null,//为了等背景地图全部加载完成再进行请求点位数据
H:"00",//时
M:"00",//分钟
S:"00",//秒
}
},
created() {
if (this.timerOne) {
clearInterval(this.timerOne)
this.timerOne = null
}
// 获取数据的那一天
this.getDataTime()
},
mounted() {
//初始化地图
this.$nextTick(() => {
this.createMap()
});
},
beforeDestroy: function () {
//实例销毁前清除于定时器
if (this.timerOne) {
clearInterval(this.timerOne)
this.timerOne = null
}
},
methods: {
// 初始化地图
createMap() {
this.scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [120.7, 29.2], //中心经度加大向右移动,纬度加大向上移动
// center: [110, 37], // 展示全国的时候用这个
// zoom: 3.8, // 展示全国的时候用这个
// minZoom: 3,// 展示全国的时候用这个
zoom: 7.1, //放大比例
maxZoom: 15,
minZoom: 4,
style: 'dark', //风格
})
})
// 创建
this.scene.on('loaded', async () => {
// 获取所有服务器数据
newgaode().then(res => {
if (res.code === 200) {
this.array = res.data
this.array4 = [...res.data]
// 最底层最暗
layer = new PointLayer({})
.source(this.array, { parser: { type: 'json', x: 'l', y: 't' } })
.shape('simple') //点的形状
.size('size', size => 4)
.color('#0F1C3E')//#0F1C3E从未开机(最暗色) #0E316F开后关机(次暗色) #2273FF开机(最亮)
.style({ opacity: 0.8, strokeWidth: 0 })
//中间层次暗层
layer2 = new PointLayer({ zIndex: 10 })
.source(this.array, { parser: { type: 'json', x: 'l', y: 't' } })
.shape('simple')
.size('size', size => 4)
.color('s', s => (s === '0' ? '#0E316F' : ''))
.style({ opacity: 0.8, strokeWidth: 0 })
// 顶层亮层
layer3 = new PointLayer({ zIndex: 20 })
.source(this.array, { parser: { type: 'json', x: 'l', y: 't' } })
.shape('simple')
.size('size', size => 4)
.color('s', s => (s === '1' ? '#2273FF' : ''))
.style({ opacity: 0.8, strokeWidth: 0 })
// 最顶层透明层
layer4 = new PointLayer({ zIndex: 30 })
.source(this.array4, { parser: { type: 'json', x: 'l', y: 't' } })
.shape('simple')
.size('size', size => size ? 13 : 4 )
.color('color', color => (color ? '#2273FF' : 'rgba(0,0,0,0)'))
.style({ opacity: 0.8, strokeWidth: 0 })
this.scene.addLayer(layer)
this.scene.addLayer(layer2)
this.scene.addLayer(layer3)
this.scene.addLayer(layer4)
this.timeLater = setTimeout(()=>{
// 多线程计时器,主线程只留一个定时器
this.onManyThread()
// 开启递归请求数据
this.getTerminalData()
// 绑定鼠标移入事件
this.mouseEnter()
clearInterval(this.timeLater)
this.timeLater = null
},2000)
}
})
})
},
// 递归请求所有时间点的数据
getTerminalData(){
this.timerOne = setTimeout(()=>{
param += 1
if (this.paramData[param]) {
this.getTerminalData()
this.handlerList(this.paramData[param])//用来请求接口
}else{
clearInterval(this.timerOne)
this.timerOne = null
}
},timeStep)
},
// 请求所有点位数据
handlerList(time){
getTerminal({time}).then(res => {
if (res.code == 200) {
const data = res.data
if(!data[0]) retun
const resKey = Object.keys(data[0])[0]
const cur = data[1][resKey]
const tod = data[2][resKey]
const bigArray=data[0][resKey]
layer2.setData(bigArray)
layer3.setData(bigArray)
// 更新左上角开机数量
this.powerOnNum(cur,tod)
}
})
},
/**
* 开机数量更新
* @param {*} current 当前开机数量
* @param {*} today 当日开机数量
*/
powerOnNum(current,today){
//更新当前开机点位的数量
let opened = current
this.configOpen.number = [Number(opened)]
this.configOpen = { ...this.configOpen }
//更新当前关机点位的数量
let cumulative = today
this.configClose.number = [Number(cumulative)]
this.configClose = { ...this.configClose }
},
/**
* 计算参数的方法
* @param {*} spacing 间隔多少分钟,单位分钟
*/
getParam(spacing){
if(!spacing) []
let timeParam = []
let count = -spacing
let count2 = 0
let interval = spacing
let length = Math.ceil(24*60 / interval)
for (let index = 0; index < length; index++) {
count += interval
let MM = count % 60
if (MM<10) {
MM = "0" + count % 60
}else{
MM = count % 60
}
let HH = Math.floor(count / 60) % 60
if (HH<10) {
HH = "0"+Math.floor(count / 60) % 60
}else{
HH = Math.floor(count / 60) % 60 == 24 ? "00" : Math.floor(count / 60) % 60
}
let startTime = `${HH}:${MM}`
count2 += interval
let MM2 = count2 % 60
if (MM2<10) {
MM2 = "0"+count2 % 60
}else{
MM2 = count2 % 60
}
let HH2 = Math.floor(count2 / 60) % 60
if (HH2<10) {
HH2 = "0"+Math.floor(count2 / 60) % 60
}else{
HH2 = Math.floor(count2 / 60) % 60 == 24 ? "00" : Math.floor(count2 / 60) % 60
}
let endTime = `${HH2}:${MM2}`
timeParam.push(`${startTime}-${endTime}`)
}
return timeParam
},
// 获取数据的那一天时间
getDataTime(){
getTime().then(res=>{
if(res.code == 200){
let date = res.data.split("-")
this.year = date[0]
this.month = date[1]
this.day = date[2]
}
})
},
// 新的绑定鼠标事件
mouseEnter(){
layer4.on('mousemove', e => {
const popup = new Popup({
offsets: [0, 0], //偏移量
closeButton: false, //是否展示右上角关闭x
maxWidth: '500px', //弹窗最大宽度
closeOnClick: true //点击地图关闭弹窗
})
.setLnglat(e.lngLat)
.setHTML(
`<span style="user-select:text;cursor:default;">终端名称: ${e.feature.d}</span><br><span style="user-select:text;cursor:default;">经纬度: ${e.feature.l} ${e.feature.t}</span>`
)
this.scene.addPopup(popup)
//鼠标划过变大点位
this.array4.forEach(item => {
item.color = item.v === e.feature.v
item.size = item.v === e.feature.v
})
layer4.setData(this.array)
})
},
// 多线程计时器
onManyThread(){
const worker = new Worker()
const totalTime = 24//展示24小时的计时
const duration = timeStep * this.paramData.length
const countNum = Number((totalTime * 60 * 60 * ste / duration).toFixed(2))//countNum步伐
worker.postMessage({code:1,ste,countNum,totalTime})//code是否开始,ste频率
worker.onmessage = e => {
const { data } = e
if (data.code === 1) {
const {HH,MM,SS} = data
this.H = HH
this.M = MM
this.S = SS
}else{
worker.terminate()
}
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
margin: -10px -12px;
.digitalFlop {
width: 160px;
height: 50px;
position: absolute;
left: 30px;
// top: 30px;
top: 90px;
z-index: 99;
}
.timeBox{
color: #fff;
font-size:58px;
top: 83px;
left: 95px;
width: 250px;
height: 58px;
line-height: 58px;
}
.date {
// background-color:green;
height: 24px;
line-height: 24px;
font-size: 22px;
color: #fff;
position: absolute;
left: 110px;
// top: 43px;
top: 150px;
z-index: 99;
}
.opend {
left: 150px;
top: 305px;
}
.closed {
width: 150px;
text-align: center;
left: 135px;
top: 458px;
}
.center {
width: 150px;
// top: 90px;
// left: 365px;
top: 245px;
left: 130px;
}
.bottom {
width: 150px;
// top: 90px;
// left: 535px;
top: 398px;
left: 130px;
}
#map {
height: 98%;
width: 100%;
justify-content: center;
::v-deep.l7-popup-tip {
border-top-color: #225cff !important;
}
::v-deep.l7-popup-content {
background-color: rgba(0, 0, 0, 0.7) !important;
border: 1px solid #225cff;
color: #226eff;
font-size: 600;
}
::v-deep.l7-control-logo {
display: none;
}
::v-deep.amap-copyright {
display: none !important;
height: 0 !important;
font-size: 0px !important;
}
}
}
</style>