Keep the UI responsive during slow work (ကြာတဲ့ အလုပ် လုပ်နေစဉ် UI ကို တုံ့ပြန်နေအောင် ထားခြင်း)

IA အတွက် tool တွေပါ၊ စာမေးပွဲ ခေါင်းစဉ် မဟုတ်ပါဘူး။ Thread (program ထဲမှာ သီးခြား run နေတဲ့ အလုပ်လမ်းကြောင်း) တွေကို သုံးတာ (threading) က IA (Internal Assessment: IB သင်တန်းမှာ ကျောင်းသားတစ်ယောက်ချင်း ကိုယ်တိုင် တည်ဆောက်ရပြီး ကျောင်းက ဆရာက အမှတ်ပေးတဲ့ အပိုင်း) အဆင့်မှာ သုံးတဲ့ နည်းစနစ် ဖြစ်ပြီး စာမေးပွဲမှာ မစစ်ပါဘူး။ ဒါတွေက ကိုယ့် IA နဲ့ ကိုက်အောင် ပြင်သုံးရမယ့် JavaFX (Java နဲ့ desktop app တည်ဆောက်တဲ့ library) code ပုံစံတွေဖြစ်ပြီး ကိုယ့် IA က ကိုယ်တိုင် လုပ်ထားတဲ့ အလုပ် ဖြစ်ရပါမယ်။

တချို့ အလုပ်တွေက အချိန်ယူပါတယ်: database (ဒေတာ သိမ်းဆည်းတဲ့ စနစ်) ထဲ save လုပ်တာ၊ network (ကွန်ရက်) ပေါ်က service တစ်ခုကို ခေါ်တာ ဒါမှမဟုတ် တွက်ချက်မှု အကြီးကြီး တစ်ခု လုပ်တာမျိုးပါ။ အဲဒီ အလုပ်ကို button ရဲ့ handler (button နှိပ်တာလို event ဖြစ်တဲ့အခါ run တဲ့ code) ထဲမှာ လုပ်ရင် အလုပ် မပြီးမချင်း window တစ်ခုလုံး ရပ်တန့် (freeze) သွားပါတယ်။ ဒါ ဘာကြောင့် ဖြစ်လဲ၊ ဒါကို ပြင်တဲ့ အခြေခံ (raw) နည်းနဲ့ Task ကို သုံးတဲ့ အကြံပြုထားတဲ့ နည်းကို ဒီစာမျက်နှာမှာ ပြပါတယ်။

ဒီ cookbook (recipe တွေကို စုထားတဲ့ လမ်းညွှန်) ကို ပထမဆုံး ဖတ်တာလား။ Start here: how every recipe fits together (ဒီမှာ စပါ: recipe တိုင်း ဘယ်လို ချိတ်ဆက်ထားလဲ) ကို အရင် ဖတ်ပါ။ အဲဒီ စာမျက်နှာက ကိုယ် ပြင်ရမယ့် နေရာ နှစ်ခုကို ရှင်းပြထားပြီး recipe (ချက်ချင်း ယူသုံးလို့ရတဲ့ code နမူနာ) တိုင်း အားကိုးရတဲ့ onAction (button နှိပ်ရင် run မယ့် method ကို သတ်မှတ်တဲ့ setting) ချိတ်ဆက်မှုကိုလည်း ရှင်းပြထားပါတယ်။

ဒီစာမျက်နှာ တစ်ခုလုံးကို ထိန်းထားတဲ့ စည်းမျဉ်း တစ်ခုတည်း: background thread (နောက်ကွယ်မှာ run နေတဲ့ thread) ကနေ UI (user interface၊ user နဲ့ program ကြားက မြင်ကွင်း) control (button၊ text box စတဲ့ user သုံးတဲ့ အစိတ်အပိုင်း) ကို ဘယ်တော့မှ မထိပါနဲ့။ Label၊ table၊ progress bar (အလုပ် ဘယ်လောက် ပြီးပြီလဲ ပြတဲ့ ဘား) လို control တစ်ခုကို ဖတ်တာ ဒါမှမဟုတ် ပြောင်းတာကို JavaFX UI thread (screen ကို ဆွဲပြီး user ရဲ့ click တွေကို ကိုင်တွယ်တဲ့ thread) ပေါ်မှာပဲ လုပ်ရပါမယ်။ အောက်က recipe တစ်ခုစီက UI thread ပေါ်ကို ဘေးကင်းကင်း ပြန်ရောက်ပုံကို ပြပါတယ်။

Setup ဘာမှ မလုပ်ဘဲ ဥပမာတွေကို run လို့ရအောင် ကြာတဲ့ လုပ်ဆောင်ချက်ကို အတုလုပ်ပြတဲ့ Thread.sleep အတိုလေး တစ်ခုကို သုံးထားပါတယ်။ Window တုံ့ပြန်နေတာနဲ့ ရပ်တန့်နေတာရဲ့ ကွာခြားချက်ကို မြင်ပြီးတာနဲ့ အဲဒါကို ကိုယ့် တကယ့် အလုပ်နဲ့ အစားထိုးပါ၊ ဥပမာ List က file နဲ့ မလောက်တော့ရင် database ကို ပြောင်းသုံးခြင်း ထဲက SQLite (file တစ်ခုတည်းထဲမှာ သိမ်းတဲ့ database အသေး) insert (row အသစ် ထည့်ခြင်း) နဲ့ပါ။ MySQL (server ပေါ်မှာ run တဲ့ database စနစ် တစ်ခု) မှာလည်း JDBC (Java ကနေ database တွေနဲ့ ချိတ်ဆက်တဲ့ စံနည်း) ပုံစံ အတူတူပါပဲ။

// Stand-in for real slow work (a database save, a network call, a heavy loop).
private void slowSave() {
    try {
        Thread.sleep(3000);   // pretend this takes 3 seconds
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

ကြာတဲ့ အလုပ်က window ကို ဘာကြောင့် ရပ်တန့်စေလဲ ကြည့်ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: ပြဿနာကို ပြပါတယ်။ ကြာတဲ့ အလုပ်ကို handler ထဲမှာ တိုက်ရိုက် run ရင် UI thread ကို ပိတ်ဆို့ထားလို့ အလုပ် မပြီးမချင်း window က မတုံ့ပြန်တော့ပါဘူး (button တွေ နှိပ်လည်း ဘာမှ မဖြစ်ဘဲ screen ကိုလည်း ပြန်မဆွဲပါဘူး)။

IA မှာ ဘယ်အချိန် သုံးမလဲ: event handler ထဲက save လုပ်ဆောင်ချက် ဒါမှမဟုတ် network request (network ပေါ်ကနေ တောင်းဆိုချက်) တစ်ခုက interface (user မြင်ပြီး သုံးရတဲ့ မျက်နှာပြင်) ကို ရပ်တန့်စေနိုင်ပါတယ်။ ဒီပုံစံကို မှတ်မိရင် အကြောင်းရင်းကို ရှာပြီး ကြာတဲ့ အလုပ်ကို background task (နောက်ကွယ်မှာ run တဲ့ အလုပ်) ဆီ ရွှေ့နိုင်ပါတယ်။

SceneBuilder မှာ (layout): SceneBuilder (FXML layout ကို ပုံနဲ့ မြင်ရင်း ပြင်တဲ့ editor) ထဲက layout (control တွေကို screen ပေါ်မှာ စီစဉ်ထားပုံ) မှာ Button တစ်ခု ထည့်ပြီး သူ့ onAction ကို handleSlowSave လို့ သတ်မှတ်ပါ။

<Button text="Save (freezes)" onAction="#handleSlowSave" />

Controller (user လုပ်တာတွေကို ကိုင်တွယ်ပြီး Model နဲ့ screen တွေကို ချိတ်ပေးတဲ့ class) မှာ (.java file):

import javafx.fxml.FXML;

@FXML
private void handleSlowSave() {
    // Runs on the UI thread: the window is frozen until slowSave() returns.
    slowSave();
}

ကိုယ်ပိုင် logic (ဘာလုပ်မလဲ ဆုံးဖြတ်တဲ့ code) ထည့်ရမယ့် နေရာ: ဒီမှာ ထည့်ဖို့ ကောင်းတဲ့ နေရာ မရှိပါဘူး။ ဒီ recipe က ကြာတဲ့ အလုပ်ကို လုပ်ရမယ့် မှားတဲ့ နေရာကို ပြဖို့ပဲ ရှိတာပါ။ ပြင်နည်းက နောက် recipe နှစ်ခုပါ၊ အဲဒီမှာ // your IA logic here (ကိုယ့် IA ရဲ့ logic ကို ဒီမှာ ထည့်ပါ) က background thread ပေါ်ကို ရွှေ့သွားပါတယ်။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • စမ်းကြည့်ပါ: alert (JavaFX မှာ အသင့်ပါတဲ့ message window) တစ်ခု ပြရုံပဲ လုပ်တဲ့ ဒုတိယ button တစ်ခု ထည့်ပါ၊ ပြီးရင် Save ကို နှိပ်ပြီး ချက်ချင်း အဲဒီ button ကို နှိပ်ပါ။ သုံးစက္ကန့် မပြည့်မချင်း ဘာမှ မဖြစ်ပါဘူး။ အဲဒီလို ရပ်တန့်နေတဲ့ window က ပြဿနာပါပဲ။

သတိထားရမယ့် အချက်များ:

  • onAction မှာ ပေးထားတဲ့ နာမည်နဲ့ method (handleSlowSave) က controller ထဲမှာ ရှိရပါမယ်။ မရှိရင် screen က load မဖြစ်ပါဘူး။
  • ရပ်တန့်နေတဲ့ window က crash (program ရုတ်တရက် ပျက်ပြီး ရပ်သွားခြင်း) မဟုတ်ပါဘူး။ UI thread က အလုပ်များလွန်းလို့ မတုံ့ပြန်နိုင်တာပါ။ နောက် recipe တွေက UI thread ကို လွတ်အောင် လုပ်ပေးပါတယ်။

တွဲသုံးလို့ရတဲ့ recipe များ: ကြာတဲ့ အလုပ်ကို background thread ပေါ်မှာ run ခြင်း: အခြေခံ နည်း၊ ပန်းတိုင်ဆီ ဘယ်လောက် ရောက်ပြီလဲ ပြခြင်း။


ကြာတဲ့ အလုပ်ကို background thread ပေါ်မှာ run ခြင်း: အခြေခံ နည်း

ဒါက ဘာလုပ်ပေးလဲ: window ဆက်တုံ့ပြန်နေအောင် ကြာတဲ့ အလုပ်ကို background thread အသစ် တစ်ခုပေါ် ရွှေ့ပြီး UI ကို update လုပ်တဲ့ အလုပ်ကို Platform.runLater နဲ့ UI thread ဆီ ပြန်လွှဲပေးပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: နောက်ကွယ်မှာ တကယ် ဘာဖြစ်နေလဲ နားလည်ချင်တဲ့ ကြာတဲ့ လုပ်ဆောင်ချက် မှန်သမျှမှာ သုံးပါတယ်။ အောက်က ပိုသပ်ရပ်တဲ့ Task version ကို မပြောင်းခင် တစ်ခါ ကြည့်ထားသင့်ပါတယ်။

SceneBuilder မှာ (layout): Button တစ်ခု (onAction ကို handleSave) နဲ့ အခြေအနေ ပြဖို့ Label တစ်ခု ထည့်ပါ။ Label ရဲ့ fx:id (screen ပေါ်က button၊ text box စတာတွေကို code ထဲက variable နဲ့ ချိတ်တဲ့ နာမည်) ကို lblStatus လို့ ပေးပါ။

<Button text="Save" onAction="#handleSave" />
<Label fx:id="lblStatus" />

Controller မှာ (.java file):

Import (တခြားနေရာက code ကို ယူသုံးမယ်လို့ ကြေညာခြင်း) line များ:

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

Controller ထဲက code:

@FXML private Label lblStatus;

@FXML
private void handleSave() {
    lblStatus.setText("Saving...");          // on the UI thread: fine

    new Thread(() -> {
        // This block runs on a background thread, so the window stays responsive.
        // your IA logic here: the slow operation
        slowSave();

        // Never touch a control directly from here. Hand the update back:
        Platform.runLater(() -> lblStatus.setText("Saved"));
    }).start();
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: background block ထဲက // your IA logic here လို့ မှတ်ထားတဲ့ line မှာ ထည့်ပါ။ Thread နဲ့ Platform.runLater နဲ့ ပြန်လွှဲပေးတာကတော့ ပြန်သုံးလို့ရတဲ့ wiring (screen နဲ့ code ကို ချိတ်ပေးတဲ့၊ ဘယ် project မှာမဆို တူတဲ့ code) ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • slowSave() ကို ကိုယ့် တကယ့် ကြာတဲ့ အလုပ်နဲ့ အစားထိုးပါ (database insert၊ download ဒါမှမဟုတ် ကြာတဲ့ တွက်ချက်မှု တစ်ခု)။
  • အလုပ် ပြီးတဲ့နောက် control တစ်ခုကို ပြောင်းမယ့် code မှန်သမျှကို Platform.runLater(...) ခေါ်မှု ထဲမှာ ထည့်ပါ။

သတိထားရမယ့် အချက်များ:

  • fx:id lblStatus က @FXML field (object ထဲက အချက်အလက် အကွက်) ရဲ့ နာမည်နဲ့ ကိုက်ရပါမယ်။ မကိုက်ရင် screen က load ဖြစ်ပေမဲ့ field က null (ဘာ object ကိုမှ မညွှန်းတဲ့ တန်ဖိုး) ဖြစ်နေပြီး Save ကို နှိပ်တဲ့အခါ error ဖြစ်ပါလိမ့်မယ်။ handleSave ကတော့ controller ထဲမှာ ရှိရပါမယ်၊ မရှိရင် screen က load မဖြစ်ပါဘူး။
  • Background thread ကနေ control ကို ဘယ်တော့မှ တိုက်ရိုက် update မလုပ်ပါနဲ့။ Platform.runLater ရဲ့ အပြင်မှာ lblStatus.setText(...) ကို ခေါ်ရင် UI ကို ပျက်စီးစေနိုင်တယ်၊ ဒါမှမဟုတ် exception (program run နေစဉ် ဖြစ်တဲ့ အမှား) ကို throw (exception ဖြစ်ကြောင်း အချက်ပြ) လုပ်နိုင်ပါတယ်။ ဒါက စာမျက်နှာ ထိပ်ဆုံးက စည်းမျဉ်း တစ်ခုတည်းပါ။
  • () -> { ... } အပိုင်းတွေက lambda (နာမည် မပါတဲ့ method အတိုလေး) တွေပါ။ Lambda တွေက စာမေးပွဲမှာ စစ်တဲ့ Java အပိုင်းထဲမှာ မပါပေမယ့် JavaFX မှာ thread တစ်ခု စတင်ဖို့နဲ့ UI update တစ်ခုကို UI thread ဆီ ပို့ဖို့ ပုံမှန် သုံးတဲ့ နည်းပါ။

တွဲသုံးလို့ရတဲ့ recipe များ: ကြာတဲ့ အလုပ်ကို Task နဲ့ run ခြင်း: အကြံပြုထားတဲ့ နည်း၊ User ကို တစ်ခုခု အသိပေးခြင်း။


ဒါက ဘာလုပ်ပေးလဲ: နောက်ကွယ်မှာ အလုပ်လုပ်ဖို့ JavaFX မှာ အသင့်ပါတဲ့ tool ဖြစ်တဲ့ javafx.concurrent.Task ကို သုံးပါတယ်။ သူ့ရဲ့ call() method က background thread ပေါ်မှာ run ပြီး setOnSucceeded နဲ့ setOnFailed ကတော့ UI thread ပေါ်မှာ ပြန် run ပါတယ်။ ဒါကြောင့် Platform.runLater ကို ကိုယ်တိုင် မရေးဘဲ UI update တွေနဲ့ error ကိုင်တွယ်တာတွေကို သပ်သပ်ရပ်ရပ် လုပ်နိုင်ပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: တကယ့် project မှာ သုံးရမယ့် version က ဒါပါ။ Task က ကြာတဲ့ အလုပ်ကို background thread ပေါ်မှာ run ပြီး အလုပ် အောင်မြင်တဲ့အခါ ဒါမှမဟုတ် မအောင်မြင်တဲ့အခါ interface ကို update လုပ်ဖို့ handler တွေ ပေးပါတယ်။

SceneBuilder မှာ (layout): Button တစ်ခု (onAction ကို handleSave) နဲ့ Label တစ်ခု (fx:id ကို lblStatus) ထည့်ပါ။

<Button text="Save" onAction="#handleSave" />
<Label fx:id="lblStatus" />

Controller မှာ (.java file):

Import line များ:

import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

Controller ထဲက code:

@FXML private Label lblStatus;

@FXML
private void handleSave() {
    Task<Void> saveTask = new Task<>() {
        @Override
        protected Void call() throws Exception {
            // Runs on a background thread. Do NOT touch controls here.
            // your IA logic here: the slow operation (e.g. a SQLite insert)
            slowSave();
            return null;
        }
    };

    // These run back on the UI thread, so updating controls here is safe.
    saveTask.setOnSucceeded(e -> lblStatus.setText("Saved"));
    saveTask.setOnFailed(e -> lblStatus.setText("Save failed"));

    lblStatus.setText("Saving...");
    new Thread(saveTask).start();
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: call() ထဲက // your IA logic here လို့ မှတ်ထားတဲ့ line မှာ ထည့်ပါ။ အဲဒါက ကိုယ့် ကြာတဲ့ လုပ်ဆောင်ချက်ပါ၊ ဥပမာ database recipe ထဲက JDBC insert။ Task၊ သူ့ရဲ့ အောင်မြင်ရင်နဲ့ မအောင်မြင်ရင် run မယ့် handler တွေနဲ့ thread ကို စတင်တာကတော့ ပြန်သုံးလို့ရတဲ့ wiring ပါ။

Hardware နဲ့ ဆက်စပ်မှု။ Background thread တွေက hardware ခေါင်းစဉ်မှာ လေ့လာရတဲ့ multi-core (core အများကြီး ပါတဲ့ CPU) processor တွေရဲ့ software ဘက်ခြမ်းပါ။ ဒီအကြောင်းကို A1.1 မှာ လေ့လာရပြီး HL မှာ pipelining (ညွှန်ကြားချက် အဆင့်တွေကို မစောင့်ဘဲ ထပ်ပြီး လုပ်ခြင်း) ကို A1.1.6 မှာ လေ့လာရပါတယ်။ Core အများ ရှိတာကြောင့် background thread က UI thread နဲ့ တစ်ပြိုင်နက် run နိုင်ပြီး အလုပ် လုပ်နေစဉ်မှာ window က ဆက်တုံ့ပြန်နေတာပါ။ Threading ကိုယ်တိုင်ကတော့ IA အဆင့် နည်းစနစ် ဖြစ်ပြီး စာမေးပွဲမှာ မစစ်ပါဘူး။ A1.1 နဲ့ A1.1.6 က IB Computer Science (နိုင်ငံတကာ ကျောင်းတွေမှာ သင်တဲ့ ကွန်ပျူတာသိပ္ပံ သင်ရိုး) ရဲ့ သင်ရိုးထဲက အပိုင်း နံပါတ်တွေပါ၊ HL (Higher Level: IB သင်တန်းမှာ ပိုနက်နက်ရှိုင်းရှိုင်း လေ့လာရတဲ့ အဆင့်) က IB သင်တန်းရဲ့ အဆင့် နှစ်ခုထဲက ပိုမြင့်တဲ့ အဆင့်ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • ကိုယ့် တကယ့် ကြာတဲ့ အလုပ်ကို call() ထဲ ထည့်ပါ။ List က file နဲ့ မလောက်တော့ရင် database ကို ပြောင်းသုံးခြင်း ထဲက SQLite insert က ဒီမှာ တိုက်ရိုက် ထည့်လို့ရပါတယ်၊ MySQL save လည်း JDBC ပုံစံ အတူတူပါပဲ။
  • အလုပ် အောင်မြင်တဲ့အခါ ဖြစ်ရမယ့် အရာအတွက် setOnSucceeded ကို သုံးပြီး call() က exception throw လုပ်တဲ့အခါအတွက် setOnFailed ကို သုံးပါ။ နှစ်ခုလုံး UI thread ပေါ်မှာ run ပါတယ်။
  • အလုပ် run နေရင်း တဖြည်းဖြည်း ပြည့်လာမယ့် progress bar အတွက် Task မှာ updateProgress(done, total) လည်း ရှိပြီး အဲဒါကို ProgressBar နဲ့ bind (တန်ဖိုး ပြောင်းတိုင်း အလိုအလျောက် လိုက်ပြောင်းအောင် ချိတ်) လုပ်နိုင်ပါတယ်။

သတိထားရမယ့် အချက်များ:

  • fx:id lblStatus က @FXML field ရဲ့ နာမည်နဲ့ ကိုက်ရပါမယ်။ မကိုက်ရင် screen က load ဖြစ်ပေမဲ့ field က null ဖြစ်နေပြီး Save ကို နှိပ်တဲ့အခါ error ဖြစ်ပါလိမ့်မယ်။ handleSave ကတော့ controller ထဲမှာ ရှိရပါမယ်၊ မရှိရင် screen က load မဖြစ်ပါဘူး။
  • call() ထဲမှာတောင် control ကို ဘယ်တော့မှ မထိပါနဲ့။ UI update အားလုံးကို setOnSucceeded / setOnFailed ထဲမှာ လုပ်ပါ၊ အဲဒါတွေကို JavaFX က ကိုယ့်အစား UI thread ပေါ်မှာ run ပေးပါတယ်။
  • Task<Void> က type parameter (ထောင့်ကွင်း < > ထဲမှာ ရေးတဲ့ type) ကို သုံးပြီး handler တွေက lambda တွေကို သုံးပါတယ်။ နှစ်ခုလုံးက စာမေးပွဲမှာ စစ်တဲ့ Java အပိုင်းထဲမှာ မပါပေမယ့် JavaFX ရဲ့ စံ နည်းလမ်းပါ။ Void ဆိုတာ task က ဘာတန်ဖိုးမှ return (ရလဒ်အဖြစ် ပြန်ပေး) မလုပ်ဘူးလို့ ဆိုလိုပါတယ်။ ရလဒ်ကို setOnSucceeded ထဲမှာ saveTask.getValue() နဲ့ ယူဖို့ လိုရင် Task<String> ကို သုံးပါ (ပြီးတော့ တန်ဖိုး တစ်ခုကို return လုပ်ပါ)။

တွဲသုံးလို့ရတဲ့ recipe များ: ပန်းတိုင်ဆီ ဘယ်လောက် ရောက်ပြီလဲ ပြခြင်း၊ List က file နဲ့ မလောက်တော့ရင် database ကို ပြောင်းသုံးခြင်း၊ User ကို တစ်ခုခု အသိပေးခြင်း။