Asynchronous Updates
Environment.runLater() API tarjoaa mekanismin käyttöliittymän turvalliseen päivittämiseen taustakelloista webforJ-sovelluksissa. Tämä kokeellinen ominaisuus mahdollistaa asynkroniset toiminnot samalla, kun se ylläpitää säikeen turvallisuutta käyttöliittymän muutoksille.
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."
Ymmärtäminen säie mallista
webforJ noudattaa tiukkaa säiemallia, jossa kaikki käyttöliittymän toiminnot on suoritettava Environment-säikeessä. Tämä rajoitus on olemassa, koska:
- webforJ API:n rajoitukset: Taustalla oleva webforJ API sitoo säikeen, joka loi istunnon
- Komponentin säie affiniteetti: Käyttöliittymäkomponentit ylläpitävät tilaa, joka ei ole säie turvallinen
- Tapahtuman käsittely: Kaikki käyttöliittymän tapahtumat käsitellään peräkkäin yhdessä säikeessä
Tämä yksisäikeinen malli estää kilpailutilanteita ja ylläpitää johdonmukaista tilaa kaikille käyttöliittymäkomponenteille, mutta luo haasteita, kun integroidaan asynkronisia, pitkäkestoisia laskentatehtäviä.
RunLater API
Environment.runLater() API tarjoaa kaksi menetelmää käyttöliittymän päivitysten ajoittamiseen:
// Ajoita tehtävä ilman palautusarvoa
public static PendingResult<Void> runLater(Runnable task)
// Ajoita tehtävä, joka palauttaa arvon
public static <T> PendingResult<T> runLater(Supplier<T> supplier)
Molemmat menetelmät palauttavat PendingResult, joka seuraa tehtävän valmistumista ja tarjoaa pääsyn tulokseen tai mahdollisiin poikkeuksiin.
Säie kontekstin perintä
Automaattinen kontekstin perintä on kriittinen ominaisuus Environment.runLater():ssa. Kun Environment:ssä toimiva 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äieestä, saa automaattisesti pääsyn kyseiseen 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
// Lastensäikeet perivät kontekstin automaattisesti
executor.submit(() -> {
String data = fetchRemoteData();
// Voit käyttää runLateria, koska konteksti perittiin
Environment.runLater(() -> {
dataLabel.setText(data);
loadingSpinner.setVisible(false);
});
});
}
}
Säikeet ilman kontekstia
Säikeet, jotka on luotu Environment-kontekstin ulkopuolella, eivät voi käyttää runLater() ja heittävät IllegalStateException:
// Staattinen alustaja - ei Environment-kontekstia
static {
new Thread(() -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}).start();
}
// Järjestelmän ajastinsäikeet - ei Environment-kontekstia
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}
}, 1000);
// Ulkoisen kirjaston säikeet - ei Environment-kontekstia
httpClient.sendAsync(request, responseHandler)
.thenAccept(response -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
});
Suoritus käyttäytyminen
runLater() suoritus käyttäytyminen riippuu siitä, mikä säie sen kutsuu:
Käyttäjäliittymäsäikeestä
Kun se kutsutaan Environment-säikeestä, 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()); // tosi
});
Tämän synkronisen käyttäytymisen myötä käyttöliittymän päivitykset tapahtumankäsittelijöistä soveltuvat heti eivätkä aiheuta tarpeettomia jonottamisoverheadia.
Taustasäikeistä
Kun se kutsutaan taustasäikeestä, tehtävät ovat jonossa asynkronista suorittamista varten:
@Override
public void onDidCreate() {
CompletableFuture.runAsync(() -> {
// Tämä toimii ForkJoinPoolin säikeessä
System.out.println("Tausta: " + Thread.currentThread().getName());
PendingResult<Void> result = Environment.runLater(() -> {
// Tämä toimii 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ä epätosi
// Tehtävä on jonossa ja suoritetaan asynkronisesti
});
}
webforJ käsittelee taustasäikeistä lähetettyjä tehtäviä tiukassa FIFO-järjestyksessä, säilyttäen toimintojen sekvenssin jopa useista säikeistä samanaikaisesti lähetettyinä. Tämän järjestyksen takuumalla käyttöliittymän päivitykset sovelletaan tarkalleen siinä järjestyksessä, jossa ne lähetettiin. 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ää käyttöliittymässä esiintyviä epäjohdonmukaisuuksia.
Tehtävän peruutus
PendingResult, jonka Environment.runLater() palauttaa, tukee peruutusta, jolloin voit estää jonossa olevien tehtävien suorittamisen. Peruuttamalla odottamat tehtävät voit välttää muistivuotoja ja estää pitkäkestoisten operaatioiden päivittämisen käyttöliittymään niiden ollessa enää tarpeettomia.
Perusperuutus
PendingResult<Void> result = Environment.runLater(() -> {
updateUI();
});
// Peruuta, jos ei ole vielä suoritettu
if (!result.isDone()) {
result.cancel();
}
Useiden päivitysten hallinta
Kun suoritat pitkäkestoisia operaatioita useilla tavoin käyttöliittymän päivityksillä, 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 mahdollista peruutusta
pendingUpdates.add(update);
Thread.sleep(100);
}
});
}
public void cancelTask() {
isCancelled = true;
// Peruuta 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), peruuta kaikki odottavat päivitykset estääksesi muistivuodot:
@Route
public class CleanupView extends Composite<Div> {
private final List<PendingResult<?>> pendingUpdates = new ArrayList<>();
@Override
protected void onDestroy() {
super.onDestroy();
// Peruuta kaikki odottavat päivitykset estääksesi muistivuotoja
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Suunnittelun huomioita
-
Kontekstivaatimus: Säikeiden on oltava perineet
Environment-konteksti. Ulkoisten kirjastojen säikeet, järjestelmäajastimet ja staattiset alustajat eivät voi käyttää tätä API:a. -
Muistivuotojen estäminen: Seuraa aina ja peruuta
PendingResult-objekteja komponentin elinkaarimenetelmissä. Jonossa olevat lambda-lausunnot kaappaavat viittauksia käyttöliittymäkomponentteihin, estäen roskankeruun, jos niitä ei peruuteta. -
FIFO-suoritus: Kaikki tehtävät suoritetaan tiukassa FIFO-järjestyksessä merkityksestä riippumatta. Prioriteettijärjestelmää ei ole.
-
Peruutuksen rajoitukset: Peruuttaminen estää vain jonossa olevien tehtävien suorittamisen. Jo käynnissä olevat tehtävät päättyvät normaalisti.
Täydellinen tapaustutkimus: LongTaskView
Seuraava on täydellinen, tuotantovalmiiksi toteutus, joka osoittaa kaikki parhaita käytäntöjä asynkronisille käyttöliittymän päivityksille:
Tapaustutkimuksen analyysi
Tämä toteutus osoittaa useita kriittisiä malleja:
1. Säietehtäviin liittyvien hallinta
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- Käyttää yksisäietehtävää resurssien loppumisen estämiseksi
- Luo daemon säikeitä, jotka eivät estä JVM:n sammuttamista
2. Odottavien päivitysten seuranta
private final List<PendingResult<?>> pendingUIUpdates = new ArrayList<>();
Jokainen Environment.runLater()-kutsu seurataan mahdollistamaan:
- Peruuttaminen, kun käyttäjä napsauttaa peruutusta
- Muistivuotojen estäminen
onDestroy():ssa - Asianmukainen puhdistus komponentin elinkaaren aikana
3. Yhteistyöperuutus
private volatile boolean isCancelled = false;
Taustasäie tarkistaa tämän lipun jokaisessa iteraatiossa, mahdollistaen:
- Välitön vastaus peruutukseen
- Puhdas poistuminen silmukasta
- Lisäkäyttöliittymäpäivitysten estäminen
4. Elinkaaren hallinta
@Override
protected void onDestroy() {
super.onDestroy();
cancelTask(); // Uudelleenkäyttää peruuttamislogiikan
currentTask = null;
executor.shutdown();
}
Kriittinen muistivuotojen estämiseksi:
- Peruuttamalla kaikki odottavat käyttöliittymän päivitykset
- Keskeyttämällä käynnissä olevat säikeet
- Samalla suorittajalla
5. Käyttöliittymän reagointikyvyn testaaminen
testButton.onClick(e -> {
int count = clickCount.incrementAndGet();
showToast("Napsautus #" + count + " - käyttöliittymä on reagoiva!", Theme.GRAY);
});
Osoittaa, että käyttöliittymäsäie pysyy reagoivana taustatoimintojen aikana.