Asynkroniset päivitykset
Environment.runLater()-rajapinta tarjoaa mekanismin käyttöliittymän turvalliseen päivittämiseen taustasäikeistä webforJ-sovelluksissa. Tämä kokeellinen ominaisuus mahdollistaa asynkroniset toiminnot ja pitää samalla käyttöliittymän muutokset säieturvallisina.
The webforj-handling-timers-and-async skill can schedule timers, debouncers, and async work safely on the UI thread. After installing the webforJ AI plugin, ask your assistant:
- "Refresh this dashboard every 30 seconds."
- "Add a search-as-you-type debouncer."
- "Run this CPU-heavy work in the background and update the progress bar."
Säiemallin ymmärtäminen
webforJ noudattaa tiukkaa säiemallia, jossa kaikkien käyttöliittymätoimintojen on tapahduttava Environment-säikeessä. Tämä rajoitus johtuu seuraavista syistä:
- webforJ-rajapinnan rajoitukset: Taustalla oleva webforJ-rajapinta sitoutuu istunnon luoneeseen säikeeseen
- Komponenttien säieaffiniteetti: Käyttöliittymäkomponentit ylläpitävät tilaa, joka ei ole säieturvallinen
- Tapahtumien käsittely: Kaikki käyttöliittymätapahtumat käsitellään peräkkäin yhdessä säikeessä
Tämä yksisäikeinen malli estää kilpajuoksutilanteita ja pitää kaikkien käyttöliittymäkomponenttien tilan johdonmukaisena, mutta aiheuttaa haasteita asynkronisten, pitkäkestoisten laskentatehtävien integroinnissa.
Jokaisella käyttäjäistunnolla on oma Environment ja oma Environment-säikeensä. Taustasäie ei kuulu mihinkään istuntoon, joten sille on kerrottava, mitä Environment-ympäristöä päivitetään.
RunLater-rajapinta
Environment.runLater()-rajapinnassa on kaksi menetelmää käyttöliittymäpäivitysten ajoittamiseen. Molemmissa ensimmäinen argumentti on kohde-Environment:
// Ajoita tehtävä, joka ei palauta arvoa
public static PendingResult<Void> runLater(Environment target, Runnable task)
// Ajoita tehtävä, joka palauttaa arvon
public static <T> PendingResult<T> runLater(Environment target, Supplier<T> supplier)
Molemmat menetelmät palauttavat PendingResult-olion, joka seuraa tehtävän valmistumista ja tarjoaa pääsyn tulokseen tai mahdollisiin poikkeuksiin.
Ylikuormitetut versiot runLater(Runnable) ja runLater(Supplier) päättelevät kohteen kutsuvan säikeen luoneen säikeen perusteella. Säiepoolissa, jota useat käyttäjät käyttävät, jokainen tehtävä suoritetaan sen käyttäjän Environment-ympäristössä, jonka kutsu loi kyseisen poolisäikeen. Nämä ylikuormitetut versiot ovat vanhentuneita versiosta 26.10 alkaen, ja ne aiotaan poistaa seuraavassa pääversiossa. Anna kohde aina eksplisiittisesti.
Kohteen välittäminen
Environment.getCurrent() palauttaa nykyisen säikeen Environment-ympäristön. Environment-säikeessä, jossa suoritetaan konstruktorit, tapahtumankäsittelijät ja elinkaarimenetelmät, se palauttaa istunnon Environment-ympäristön. Taustasäikeessä se palauttaa null.
Lue kohde paikassa, jossa se on saatavilla, ennen taustatyön käynnistymistä, ja välitä se työlle:
@Route
public class DataView extends Composite<Div> {
private final ExecutorService executor = Executors.newCachedThreadPool();
public DataView() {
// Konstruktori suoritetaan Environment-säikeessä
Environment env = Environment.getCurrent();
executor.submit(() -> {
String data = fetchRemoteData();
Environment.runLater(env, () -> {
dataLabel.setText(data);
loadingSpinner.setVisible(false);
});
});
}
}
Koska kohde välitetään argumenttina, kutsu toimii mistä tahansa säikeestä: näkymän luomasta säikeestä, Springin @Async-säikeestä, yhteisestä ForkJoinPool-poolista, ajastinsäikeestä tai HTTP-asiakasohjelman takaisinkutsusäikeestä.
Kohteen lukeminen väärässä säikeessä
Vältä lukemasta Environment.getCurrent()-arvoa taustatyön sisällä. Siellä se palauttaa null, jolloin runLater() heittää NullPointerException-poikkeuksen:
executor.submit(() -> {
String data = fetchRemoteData();
// Väärin: getCurrent() palauttaa taustasäikeessä null
Environment.runLater(Environment.getCurrent(), () -> dataLabel.setText(data));
});
Sama koskee staattisia alustajia, java.util.Timer-säikeitä ja ulkoisten kirjastojen takaisinkutsuja. Mikään niistä ei toimi Environment-ympäristössä, joten kohteen on tultava kentästä, parametrina tai paikallisesta muuttujasta, jonka arvo tallennettiin Environment-säikeessä.
Suorituskäyttäytyminen
runLater()-menetelmän suorituskäyttäytyminen riippuu sitä kutsuvasta säikeestä:
Käyttöliittymäsäikeestä
Kun menetelmää kutsutaan kohteen omasta Environment-säikeestä, tehtävät suoritetaan synkronisesti ja välittömästi:
button.onClick(e -> {
Environment env = Environment.getCurrent();
System.out.println("Ennen: " + Thread.currentThread().getName());
PendingResult<String> result = Environment.runLater(env, () -> {
System.out.println("Sisällä: " + Thread.currentThread().getName());
return "valmis";
});
System.out.println("Jälkeen: " + result.isDone()); // true
});
Tämän synkronisen toiminnan ansiosta tapahtumankäsittelijöiden käyttöliittymäpäivitykset otetaan käyttöön heti, eikä niistä aiheudu tarpeetonta jonotuskuormaa.
Taustasäikeistä
Kun menetelmää kutsutaan mistä tahansa muusta säikeestä, tehtävät jonotetaan suoritettaviksi asynkronisesti:
@Override
public void onDidCreate() {
Environment env = Environment.getCurrent();
CompletableFuture.runAsync(() -> {
// Tämä suoritetaan ForkJoinPool-säikeessä
System.out.println("Tausta: " + Thread.currentThread().getName());
PendingResult<Void> result = Environment.runLater(env, () -> {
// Tämä suoritetaan Environment-säikeessä
System.out.println("Käyttöliittymän päivitys: " + Thread.currentThread().getName());
statusLabel.setText("Käsittely valmis");
});
// result.isDone() olisi tässä false
// Tehtävä on jonossa ja suoritetaan asynkronisesti
});
}
webforJ käsittelee taustasäikeistä lähetetyt tehtävät tiukassa FIFO-järjestyksessä ja säilyttää näin toimintojen järjestyksen myös silloin, kun useat säikeet lähettävät tehtäviä samanaikaisesti. Tämän järjestystakuun ansiosta käyttöliittymäpäivitykset otetaan käyttöön täsmälleen siinä järjestyksessä, jossa ne lähetettiin. Jos siis säie A lähettää tehtävän 1 ja sen jälkeen säie B lähettää tehtävän 2, tehtävä 1 suoritetaan aina ennen tehtävää 2 käyttöliittymäsäikeessä. Tehtävien käsittely FIFO-järjestyksessä estää käyttöliittymän epäjohdonmukaisuudet.
Tehtävien peruminen
Environment.runLater()-menetelmän palauttama PendingResult tukee perumista, joten voit estää jonossa olevien tehtävien suorittamisen. Jonossa olevien tehtävien peruminen auttaa välttämään muistivuotoja ja estää pitkäkestoisia toimintoja päivittämästä käyttöliittymää sen jälkeen, kun niitä ei enää tarvita.
Perumisen perusteet
PendingResult<Void> result = Environment.runLater(env, () -> {
updateUI();
});
// Peru, jos tehtävää ei ole vielä suoritettu
if (!result.isDone()) {
result.cancel();
}
Useiden päivitysten hallinta
Kun suoritat pitkäkestoisia toimintoja, joihin liittyy usein tehtäviä käyttöliittymäpäivityksiä, seuraa kaikkia odottavia tuloksia:
public class LongRunningTask {
private final List<PendingResult<?>> pendingUpdates = new ArrayList<>();
private volatile boolean isCancelled = false;
public void startTask() {
Environment env = Environment.getCurrent();
CompletableFuture.runAsync(() -> {
for (int i = 0; i <= 100; i++) {
if (isCancelled) return;
final int progress = i;
PendingResult<Void> update = Environment.runLater(env, () -> {
progressBar.setValue(progress);
});
// Seuraa mahdollista perumista varten
pendingUpdates.add(update);
Thread.sleep(100);
}
});
}
public void cancelTask() {
isCancelled = true;
// Peru kaikki odottavat käyttöliittymäpäivitykset
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Komponentin elinkaaren hallinta
Kun komponentteja tuhotaan (esimerkiksi siirryttäessä toiseen näkymään), peru kaikki odottavat päivitykset muistivuotojen välttämiseksi:
@Route
public class CleanupView extends Composite<Div> {
private final List<PendingResult<?>> pendingUpdates = new ArrayList<>();
@Override
protected void onDestroy() {
super.onDestroy();
// Peru kaikki odottavat päivitykset muistivuotojen välttämiseksi
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Suunnitteluun liittyviä huomioita
-
Kohdevaatimus: Kohde-
EnvironmentluetaanEnvironment.getCurrent()-menetelmälläEnvironment-säikeessä ja välitetään taustatyölle. Sen lukeminen taustasäikeessä palauttaanull. -
Muistivuotojen ehkäisy: Seuraa ja peru aina
PendingResult-oliot komponentin elinkaarimenetelmissä. Jonotetut lambdat säilyttävät viittauksia käyttöliittymäkomponentteihin ja estävät niiden roskienkeruun, jos niitä ei peruta. -
FIFO-suoritus: Kaikki tehtävät suoritetaan tiukassa FIFO-järjestyksessä niiden tärkeydestä riippumatta. Prioriteettijärjestelmää ei ole.
-
Perumisen rajoitukset: Peruminen estää vain jonossa olevien tehtävien suorittamisen. Jo suorituksessa olevat tehtävät suoritetaan normaalisti loppuun.
Täydellinen tapaustutkimus: LongTaskView
Seuraava on tuotantokäyttöön valmis toteutus, joka havainnollistaa asynkronisten käyttöliittymäpäivitysten parhaita käytäntöjä:
Tapaustutkimuksen analyysi
Toteutus havainnollistaa useita keskeisiä toimintamalleja:
1. Säiepoolin hallinta
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- Käyttää yksisäikeistä suorittajaa resurssien ehtymisen estämiseksi
- Luo demonisäikeitä, jotka eivät estä JVM:n sulkemista
2. Kohteen nimeäminen
Environment env = Environment.getCurrent();
startLongTask() suoritetaan Environment-säikeessä, joten se lukee kohteen siinä, minkä jälkeen taustatehtävä välittää sen jokaiseen Environment.runLater()-kutsuun. Sama koodi toimii muuttumattomana myös sovelluksen yhteisessä säiepoolissa.
3. Odottavien päivitysten seuraaminen
private final List<PendingResult<?>> pendingUIUpdates = new ArrayList<>();
Jokainen Environment.runLater()-kutsu kirjataan, jotta voidaan:
- Perua tehtäviä, kun käyttäjä napsauttaa perumispainiketta
- Ehkäistä muistivuotoja
onDestroy()-metodissa - Siivota asianmukaisesti komponentin elinkaaren aikana
4. Yhteistoiminnallinen peruminen
private volatile boolean isCancelled = false;
Taustasäie tarkistaa tämän lipun jokaisella iteraatiolla, mikä mahdollistaa:
- Nopean reagoinnin perumiseen
- Siistin poistumisen silmukasta
- Uusien käyttöliittymäpäivitysten estämisen
5. Elinkaaren hallinta
@Override
protected void onDestroy() {
super.onDestroy();
cancelTask(); // Käyttää perumislogiikkaa uudelleen
currentTask = null;
executor.shutdown();
}
Tämä on keskeistä muistivuotojen ehkäisemisessä, sillä se:
- Peruu kaikki odottavat käyttöliittymäpäivitykset
- Keskeyttää käynnissä olevat säikeet
- Sulkee suorittajan
6. Käyttöliittymän responsiivisuuden testaus
testButton.onClick(e -> {
int count = clickCount.incrementAndGet();
showToast("Napsautus #" + count + " – käyttöliittymä toimii responsiivisesti!", Theme.GRAY);
});
Osoittaa, että käyttöliittymäsäie pysyy responsiivisena taustatoimintojen aikana.


