Wielokrotnie implementowałem wielowątkowość w C++ i C#, teraz chciałbym w JavaScript w sposób podobny do C#. Za każdym razem, jak szukam coś o wątkach, to dostaję informację, żeby użyć Worker. Zrobiłem prosty i kompletny plik HTML zawierający skrypt.
Oczekuję rzeczy następującej:
- "Start infinity" - uruchamia pętlę w nieskończoność, pętla pracuje tak długo, aż kliknę "Stop".
- "Start 20 times" - uruchamia pętlę na 20 iteracji (trwa ok. 10 sekund) lub do kliknięcia "Stop" w zależności, co wcześniej nastąpi.
- "Stop" - sprawia, że warunek pętli przestaje być spełniony i aktualna iteracja pętli jest ostatnią.
- "Status" - wypisuje do konsoli przeglądarki stan, czy pętla pracuje i łączną liczbę iteracji od uruchomienia programu, powinien działać niezależnie od tego, czy pętla pracuje.
W rzeczywistości jest niestety tak:
- "Start infinity" - po jego kliknięciu żaden przycisk nie działa.
- "Start 20 times" - po jego kliknięciu żaden przycisk nie działa do czasu zakończenia pracy.
- "Stop" - nie działa, nic się nie dzieje po kliknięciu.
- "Status" - działa tylko w przypadku braku pracy, a w przypadku pracy, czeka na zakończenie pętli.
Co muszę zmienić, żeby wszystkie przyciski działały zgodnie z oczekiwaniem?
Wygląda na to, że zamiast wywoływać się od razu DemoObj.postMessage
, one są kolejkowane i wywoływane tylko wtedy, gdy wątek nie pracuje. Pytanie sprowadza się do tego: Jak zmusić Worker
, żeby wywołania DemoObj.postMessage
wykonywał od razu?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style type="text/css">
</style>
</head>
<body>
<input type="button" value="Start infinity" onclick="DemoStart1()" />
<input type="button" value="Start 20 times" onclick="DemoStart2()" />
<input type="button" value="Stop" onclick="DemoStop()" />
<input type="button" value="Status" onclick="DemoStatus()" />
<script type="text/javascript">
// Skrypt workera jako funkcja, żeby można było załączyć w bieżącym pliku HTML
function DemoDef()
{
let Working;
let LoopCounter = 0;
this.onmessage = function(Evt)
{
switch (Evt.data.Cmd)
{
case 0:
{
Start(-1);
}
return;
case 3:
{
Start(20);
}
return;
case 1:
{
Stop();
}
return;
case 2:
{
GetStatus();
}
return;
}
}
// Uruchomienie pętli, dodatni parametr przekazuje liczbę iteracji,
// ujemy parametr znaczy wywołanie pętli nieskończonej
function Start(C)
{
C = C + LoopCounter;
Working = true;
// Pętla pracuje dopóki Working=true, osobna funkcja
// zmienia na Working=false, co powoduje zakończenie pętli
while (Working)
{
// Symulacja obliczeń trwających pół sekundy
let T = performance.now();
T = T + 500;
while (T > performance.now())
{
}
// Zliczenie iteracji
LoopCounter++;
// Jezeli parametr jest dodatni, to należy opuścić pętlę po
// takiej liczbie iteracji, ile wynosi parametr
if (C > 0)
{
if (LoopCounter == C)
{
Working = false;
}
}
}
}
// Zatrzymanie pętli
function Stop()
{
Working = false;
}
// Wysłanie statusu workera
function GetStatus()
{
let StatusInfo = "Timestamp: " + performance.now();
StatusInfo = StatusInfo + "\nWorking status: " + (Working ? "busy" : "idle");
StatusInfo = StatusInfo + "\nLoop counter: " + LoopCounter;
postMessage({ result: StatusInfo });
}
}
// Konwersja skryptu workera na blob i tworzenie obiektu workera
let DemoObj = new Worker(URL.createObjectURL(new Blob(["("+DemoDef.toString()+")()"], {type: 'text/javascript'})));
// Wywolanie zwrotne workera
DemoObj.onmessage = function(Data)
{
console.log(Data.data.result);
}
// Wywolanie zwrotne w przypadku błędu
DemoObj.onerror = function(Data)
{
let ErrMsg = "Error: " + Data.message;
ErrMsg = ErrMsg + "\n line: " + Data.lineno;
ErrMsg = ErrMsg + "\n file: " + Data.filename;
console.log(ErrMsg);
}
// Uruchomienie nieskończonej pętli
function DemoStart1()
{
DemoObj.postMessage({Cmd:0});
}
// Uruchomienie 20 iteracji pętli (ok. 10 sekund)
function DemoStart2()
{
DemoObj.postMessage({Cmd:3});
}
// Zatrzymanie pętli
function DemoStop()
{
DemoObj.postMessage({Cmd:1});
}
// Zapytanie o status workera
function DemoStatus()
{
DemoObj.postMessage({Cmd:2});
}
</script>
</body>
</html>