Asynchronous Updates
Environment.runLater() API tarjoaa mekanismin käyttöliittymän turvalliselle päivittämiselle taustaprosesseista webforJ-sovelluksissa. Tämä kokeellinen ominaisuus mahdollistaa asynkroniset toiminnot säilyttäen samalla säie turvallisuuden käyttöliittymän muokkauksille.
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."
Thread-mallin ymmärtäminen
webforJ noudattaa tiukkaa säiemallia, jossa kaikki käyttöliittymätoiminnot on suoritettava Environment säikeessä. Tämä rajoitus johtuu siitä, että:
- webforJ API -rajoitukset: Alus webforJ API sitoutuu säikeeseen, joka loi istunnon
- Komponenttien säie affiniteetti: käyttöliittymäkomponentit ylläpitävät tilaa, joka ei ole säie turvallinen
- Tapahtumien käsittely: Kaikki käyttöliittymä tapahtumat käsitellään peräkkäin yhdellä säikeellä
Tämä yksisäie malli estää kilpailutilanteita ja ylläpitää johdonmukaista tilaa kaikille käyttöliittymäkomponenteille, mutta aiheuttaa haasteita asynkronisten, pitkäkestoisten laskentatehtävien integroimisessa.
RunLater API
Environment.runLater() API tarjoaa kaksi menetelmää käyttöliittymän päivitysten ajoittamiseen:
// Aikatauluta tehtävä ilman palautusarvoa
public static PendingResult<Void> runLater(Runnable task)
// Aikatauluta tehtävä, joka palauttaa arvon
public static <T> PendingResult<T> runLater(Supplier<T> supplier)
Molemmat menetelmät palauttavat PendingResult -objektin, joka seuraa tehtävän valmistumista ja tarjoaa pääsyn tulokseen tai mahdollisiin poikkeuksiin.
Säieyhteyden perintö
Automaattinen yhteyden perintö on kriittinen ominaisuus Environment.runLater():ssä. Kun Environment-säikeessä oleva säie luo lapsisäikeitä, nämä lapset perivät automaattisesti kyvyn käyttää runLater().
Kuinka perintö toimii
Mikä tahansa säie, joka luodaan Environment-säikeen sisällä, pääsee automaattisesti siihen Environment:iin. Tämä perintö tapahtuu automaattisesti, joten sinun ei tarvitse siirtää mitään kontekstia tai konfiguroida mitään.
@Route
public class DataView extends Composite<Div> {
private final ExecutorService executor = Executors.newCachedThreadPool();
public DataView() {
// Tässä säikeessä on Environment-konteksti
// Lapsisäikeet perivät kontekstin automaattisesti
executor.submit(() -> {
String data = fetchRemoteData();
// Voit käyttää runLater, koska konteksti perittiin
Environment.runLater(() -> {
dataLabel.setText(data);
loadingSpinner.setVisible(false);
});
});
}
}
Kontekstittomat säikeet
Säikeet, jotka on luotu Environment-kontekstin ulkopuolella, eivät voi käyttää runLater() ja heitetään IllegalStateException:
// Staattinen alustaja - ei Environment-kontekstia
static {
new Thread(() -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}).start();
}
// Järjestelmätimerisäikeet - ei Environment-kontekstia
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}
}, 1000);
// Ulkoiset kirjastosäikeet - ei Environment-kontekstia
httpClient.sendAsync(request, responseHandler)
.thenAccept(response -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
});
Suoritus käyttäytyminen
runLater()-menetelmän suoritus käyttäytyminen riippuu siitä, mikä säie kutsuu sitä:
Käyttöliittymäsäikeestä
Kun sitä kutsutaan Environment-säikeestä itsestään, tehtävät suoritetaan synkronisesti ja heti:
button.onClick(e -> {
System.out.println("Ennen: " + Thread.currentThread().getName());
PendingResult<String> result = Environment.runLater(() -> {
System.out.println("Sisällä: " + Thread.currentThread().getName());
return "valmis";
});
System.out.println("Jälkeen: " + result.isDone()); // true
});
Tämän synkronisen käyttäytymisen ansiosta käyttöliittymän päivitykset tapahtuvat heti tapahtumakäsittelijöistä eikä aiheuta tarpeetonta jonotusta.
Taustasäikeistä
Kun kutsutaan taustasäikeestä, tehtävät jonotetaan asynkronista suorittamista varten:
@Override
public void onDidCreate() {
CompletableFuture.runAsync(() -> {
// Tämä suoritetaan ForkJoinPool -säikeessä
System.out.println("Tausta: " + Thread.currentThread().getName());
PendingResult<Void> result = Environment.runLater(() -> {
// 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 false täällä
// Tehtävä on jonotettu ja suoritetaan asynkronisesti
});
}
webforJ käsittelee taustasäikeistä lähetettyjä tehtäviä tiukka FIFO-järjestyksessä, säilyttäen toimintojen järjestyksen, vaikka niitä lähetettäisiin useista säikeistä samalla. Tällä järjestyksen takuulla käyttöliittymän päivitykset tapahtuvat täsmälleen siinä järjestyksessä, jossa ne on lähetetty. Joten jos säie A lähettää tehtävän 1, ja sitten säie B lähettää tehtävän 2, tehtävä 1 suoritetaan aina ennen tehtävää 2 käyttöliittymässä. Tehtävien käsittely FIFO-järjestyksessä estää epäjohdonmukaisuuksia käyttöliittymässä.
Tehtävän peruutus
PendingResult, jonka Environment.runLater() palauttaa, tukee peruutusta, jolloin voit estää jonotettujen tehtävien suorittamisen. Perumalla odottavat tehtävät voit välttää muistivuodot ja estää pitkäkestoisten toimintojen päivittämisen käyttöliittymään, kun niitä ei enää tarvita.
Perusperuutus
PendingResult<Void> result = Environment.runLater(() -> {
updateUI();
});
// Peru, jos ei ole vielä suoritettu
if (!result.isDone()) {
result.cancel();
}
Useiden päivitysten hallinta
Kun suoritat pitkäkestoisia toimintoja, joissa on tiheä käyttöliittymäpäivitys, seuraa kaikkia odottavia tuloksia:
public class LongRunningTask {
private final List<PendingResult<?>> pendingUpdates = new ArrayList<>();
private volatile boolean isCancelled = false;
public void startTask() {
CompletableFuture.runAsync(() -> {
for (int i = 0; i <= 100; i++) {
if (isCancelled) return;
final int progress = i;
PendingResult<Void> update = Environment.runLater(() -> {
progressBar.setValue(progress);
});
// Seuraa mahdolliselle peruutukselle
pendingUpdates.add(update);
Thread.sleep(100);
}
});
}
public void cancelTask() {
isCancelled = true;
// Peru kaikki odottavat käyttöliittymän päivitykset
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Komponentin elinkaaren hallinta
Kun komponentit tuhotaan (esim. navigoinnin aikana), peru kaikki odottavat päivitykset estääkseen muistivuodot:
@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 estääkseen muistivuodot
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Suunnittelun huomioita
-
Kontekstivaatimus: Säikeiden on oltava perineitä
Environment-konteksti. Ulkoiset kirjastosäikeet, järjestelmätimerit ja staattiset alustajat eivät voi käyttää tätä API:a. -
Muistivuodon estäminen: Seuraa aina ja peru
PendingResult-objekteja komponenttien elinkaarimenettelyissä. Jonotetut lambdat napsauttavat viittauksia käyttöliittymäkomponentteihin, estäen roskankeruun, ellet peruuta. -
FIFO-suoritus: Kaikki tehtävät suoritetaan tiukassa FIFO-järjestyksessä merkityksestä riippumatta. Prioriteettijärjestelmää ei ole.
-
Peruutuksen rajoitukset: Peruuttaminen estää vain jonotettujen tehtävien suorittamisen. Jo suorittavat tehtävät suoritetaan normaalisti.
Täydellinen tapaustutkimus: LongTaskView
Seuraava on täydellinen, tuotantovalmius toteutus, joka demonstroi kaikkia parhaita käytäntöjä asynkronisille käyttöliittymän päivityksille:
Tapaustutkimuksen analyysi
Tämä toteutus demonstroi useita kriittisiä malleja:
1. Säietehtävän hallinta
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- Käyttää yksittäistä säietehtävää estääkseen resurssien kulumisen
- Luo daemon-säikeitä, jotka eivät estä JVM:ää sulkeutumasta
2. Odottavien päivitysten seuraaminen
private final List<PendingResult<?>> pendingUIUpdates = new ArrayList<>();
Jokainen Environment.runLater()-kutsu seurataan mahdollistamaan:
- Peruuttaminen, kun käyttäjä napsauttaa peruuta
- Muistivuodon estäminen
onDestroy():ssä - Oikea puhdistus komponentin elinkaaren aikana
3. Yhteistyöperuutus
private volatile boolean isCancelled = false;
Taustasäie tarkistaa tämän lipun jokaisessa iteraatiossa, mikä mahdollistaa:
- Välittömät reaktiot peruutukseen
- Siisti poistuminen silmukasta
- Estää lisäkäyttöliittymän päivitykset
4. Elinkaaren hallinta
@Override
protected void onDestroy() {
super.onDestroy();
cancelTask(); // Uudelleenkäyttää peruutuslogiikan
currentTask = null;
executor.shutdown();
}
Kriittistä muistivuotojen estämiseksi:
- Peru kaikkia odottavia käyttöliittymän päivityksiä
- Keskeyttää käynnissä olevat säikeet
- Sulje täytäntöönpanot rauhallisesti
5. Käyttöliittymän responsiivisuuden testaus
testButton.onClick(e -> {
int count = clickCount.incrementAndGet();
showToast("Napsautus #" + count + " - Käyttöliittymä on responsiivinen!", Theme.GRAY);
});
Demonstroi, että käyttöliittymäsäie pysyy responsiivisena taustatoimien aikana.