iT邦幫忙

0

javascript耗時間的計算動作造成畫面阻塞的解決方式?

  • 分享至 

  • xImage

有一個過程需要經過多個function計算後才能得出結果
而這一個過程會每隔一段時間就loop一次
ex.
第2個function需要第1個function計算過的資料
第3個function需要第2個function計算過的資料
以此類推...
這些function需要用同步的方式順序執行
最後會需要用計算出資料繪製在canvas上顯示出來

假設1個function可能要花掉50~100ms
因此會造成畫面上的狀態有卡頓的狀況發生
目前只想到將計算過程放到settimeout的callback中

這是我的範例
一個不斷上下移動的方塊,顯示計算對畫面造成阻塞的影響

<style>
.rectangle {
	position: absolute;
	left: 50%;
	width: 150px;
	height: 150px;
	transform: translateX(-50%);
	text-align: center;
	background-color: #2196f3;
	animation: move  infinite  5s  linear;
}
@keyframes  move {
	0% {top: 5%;background-color: #2196f3;}
	50% {top: 80%;background-color: #9e9e9e;}
	100% {top: 5%;background-color: #2196f3;}
}
</style>
<body>
	<div  class="rectangle">TEST</div>
	<div  id="state"></div>
</body>
function  computeProcess(len) {
	state.innerHTML  =  ""
	for (let  i  =  0; i  <  len; i++) {
		state.innerHTML  =  i;
	}
}
function  timeOut(callback, timer) {
	setTimeout(() => {
		callback();
	}, timer);
}
function  loop() {
    // 會阻塞造成畫面卡頓
	setTimeout(() => {
		computeProcess(10000); // 第1個function
		computeProcess(10000); // 第2個function
		computeProcess(10000); // 第3個function
		loop();
	}, 300);
	// 解決卡頓的作法???
	timeOut(() => {
		computeProcess(10000);
		timeOut(() => {
			computeProcess(10000);
			timeOut(() => {
			computeProcess(10000);
				timeOut(() => {
					loop();
				}, 300);
			}, 0);
		}, 0);
	}, 0);
}
loop();

但這樣變成Callback Hell,所以我現在是改成這樣做
讓function照順序執行然後也不要畫面有卡頓的狀況發生
不知道有沒有更好的方法?

function timeOut(callback, timer) {
	return new Promise((resolve) => {
		setTimeout(() => {
			callback();
			resolve();
		}, timer);
	});
}
function loop(){
	(async () => {
		await  timeOut(() => {
			computeProcess(10000);
		}, 0);
		await  timeOut(() => {
			computeProcess(10000);
		}, 0);
		await  timeOut(() => {
			computeProcess(10000);
		}, 0);
		await  timeOut(() => {
			loop();
		}, 300);
	})();
}
看更多先前的討論...收起先前的討論...
你的問題好像跟範例不太一樣?
範例是一直重複調用 DOM API
問題是計算完才調用 DOM API
這在頻率上就會有很顯著的不一樣
應該是說範例是想表達計算耗時影響到界面的問題

實際的問題是
我用opencv.js對圖像做計算(耗時)得出資料
然後把資料繪製(opencv.js api)在canvas上顯示出來
每隔0.5秒就就要計算然後畫出來
一個loop可能會有3到4個步驟
灰度化、二值化、高斯模糊、findcontours之類的計算

這些的動作被我集中到一個function執行
所以會顯示"handler 需要 <N> 毫秒"警告一個function花太多時間

如果耗時間的計算之間
空出一個空檔讓瀏覽器可以重繪GUI理論上應該就不會有UI卡頓的感覺
我覺得淺水員大的方法比較好
本來想說用 rxjs 不過運算量大一樣會影響到畫面渲染
froce iT邦大師 1 級 ‧ 2021-12-24 16:29:25 檢舉
運算量大是一定要丟給worker跑,但是他如果不會寫async還是會遇到得寫一堆setTimeout的問題吧...
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
Felix
iT邦研究生 2 級 ‧ 2021-12-24 12:09:53

30 Seconds of Knowledge - chainAsync

如果我沒理解錯誤的話,您或許能參考上方的資源。

5
淺水員
iT邦大師 6 級 ‧ 2021-12-24 14:08:28

Web Workers 提供簡單的方法讓網頁在背景執行緒 (Thread) 中執行程式

運算量大的東西可以考慮丟給 Web Worker

0
froce
iT邦大師 1 級 ‧ 2021-12-24 15:07:26
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div id="r">處理中</div>
</body>
</html>

<script>
  function delay(n){
      return new Promise(function(resolve){
          setTimeout(resolve,n*1000);
      });
  }
  
  let fn1 = async () => {
    await delay(3)  // delay請當成大量運算的時間
    return 3
  }
  
  let fn2 = async (i) => {
    await delay(2)
    return i * 3
  }
  
  // 統合fn1、fn2,並等待fn2完成後發送事件告訴傳入的DOM該更新了。
  let fn3 = async (elm) => {
    const r1 = await fn1()
    const r2 = await fn2(r1)
    elm.dispatchEvent(new CustomEvent("onResult", {
        detail: {r2: r2}
      }));
  }
  
  const resultDisplay = document.querySelector("#r")
  // 註冊事件
  resultDisplay.addEventListener("onResult", function(e){
    this.innerText = e.detail.r2
  })
  
  // 執行
  fn3(resultDisplay)
</script>

https://jsbin.com/yujenaheti/edit?html,output
老慣例,ie一樣不能跑。

你要的大概是這樣吧。
delay的部份只是為了模擬運算時間。
真正大量運算的話用上面說的 Web Workers。

然後在javascript請盡量用event代替setTimeout...
你能確定你的運算不會delay到連你timeout的時間都超過?


稍微改一下code結構,為了解耦合。

看更多先前的回應...收起先前的回應...

不確定會不會delay到,不太清楚用event代替settimeout的意思是甚麼。

想問個,只要把耗時間的計算動作拆開,讓中間產生空檔讓GUI有空檔可以重新重繪,我的理解不知有沒有錯。

我能拆分的最小單位就是opencv.js的api,一個api的花掉的時間應該不多。

froce iT邦大師 1 級 ‧ 2021-12-24 15:52:37 檢舉

你注意看我的code,除了為了模擬大量運算的delay,應該沒有用到setTimeout的地方吧?

如果你都用setTimeout去等運算完成,你會遇到幾個問題:

  1. 你的timeout是否足夠?js是單執行序,會不會因為你重複執行造成程序堵塞,讓timeout超過?
  2. 你的timeout會不會太大,造成浪費?

因此最好的策略就是運算完就反應,而不是用setTimeout這種你無法掌握的方式。
如果你需要固定時序更新,可以先存到一個cache,定時取出更新。

我的code簡單的說就是首先先對#r註冊一個自訂事件(onResult),當這事件發生時,會更新畫面。
用fn3去等fn1、2的依序運算完成,觸發(dispatchEvent)一個自訂事件(onResult)。

感恩!

所以就是把耗時間的動作(opencv api)放入fn1、fn2、fn3 ~ fnN 以此類推
fn裡面的api處理完資料就立刻return promise
最後在onResult裡面拿到我最後要的結果

這樣還可以有空閒渲染我的畫面嗎,因為我只是想說介面(方塊移動)不要卡頓

froce iT邦大師 1 級 ‧ 2021-12-24 19:56:51 檢舉

你自己試試看囉

fillano iT邦超人 1 級 ‧ 2021-12-24 22:00:30 檢舉

我覺得還是優先丟到Web Worker跑才真的解決問題...計算量大時,就不要讓他佔走main thread的時間。而且worker跟main thread也都是透過事件在溝通(onmessage/postMessage),也容易結合froce建議的做法來進行。

我要發表回答

立即登入回答