Asynchronous Updates
Environment.runLater()
API tarjoaa mekanismin käyttöliittymän turvalliseen päivittämiseen taustasäikeistä webforJ-sovelluksissa. Tämä kokeellinen toiminto mahdollistaa asynkroniset toiminnot samalla säilyttäen säiekohtaisen turvallisuuden käyttöliittymään liittyville muutoksille.
Tämä API on merkitty kokeelliseksi 25.02 alkaen ja voi muuttua tulevissa julkaisuissa. API:n allekirjoitus, käyttäytyminen ja suorituskykyominaisuudet saattavat muuttua.
Säiemalli
webforJ valvoo tiukkaa säiemallia, jossa kaikki käyttöliittymätoiminnot on suoritettava Environment
-säikeessä. Tämä rajoitus on olemassa seuraavista syistä:
- webforJ API -rajoitukset: Taustalla oleva webforJ API sitoutuu säikeeseen, joka loi istunnon.
- Komponenttien säidehtivät: Käyttöliittymäkomponentit ylläpitävät tilaa, joka ei ole säiekohtaista.
- Tapahtumien käsittely: Kaikki käyttöliittymätapahtumat käsitellään järjestyksessä yhdessä säikeessä.
Tämä yksisäikeinen malli estää kilpailutilanteet ja ylläpitää johdonmukaista tilaa kaikille käyttöliittymäkomponenteille, mutta luo haasteita asynkronisten, pitkäkestoisten laskentatehtävien integroimiseen.
RunLater
API
Environment.runLater()
API tarjoaa kaksi menetelmää käyttöliittymän päivitysten ajoittamiseksi:
// 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 suorittamista ja tarjoaa pääsyn tulokseen tai mahdollisiin poikkeuksiin, jotka ilmenivät.
Säiedelegointi
Automaattinen kontekstin siirtäminen on kriittinen ominaisuus Environment.runLater()
:ssa. Kun Environment
-säikeessä luodaan lapsisäikeitä, nämä lapset periävät automaattisesti kyvyn käyttää runLater()
.
Kuinka perintä toimii
Mikä tahansa säie, joka luodaan Environment
-säikeestä, saa automaattisesti pääsyn 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ämä säie on Environment-kontekstissa
// Lastensäikeet perivät kontekstin automaattisesti
executor.submit(() -> {
String data = fetchRemoteData();
// Voidaan käyttää runLater, koska konteksti periytyi
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()
-metodia ja aiheuttavat IllegalStateException
-virheen:
// Staattinen inicialisoija - 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);
// Ulkoisten kirjasto-säikeet - ei Environment-kontekstia
httpClient.sendAsync(request, responseHandler)
.thenAccept(response -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
});
Suorituskyky
runLater()
-metodin suorituskyky riippuu siitä, mikä säie kutsuu sitä:
Käyttöliittymässä
Kun kutsutaan Environment
-säikeestä, tehtävät suoritetaan synkronisesti ja välittömästi:
button.onClick(e -> {
System.out.println("Ennen: " + Thread.currentThread().getName());
PendingResult<String> result = Environment.runLater(() -> {
System.out.println("Sisällä: " + Thread.currentThread().getName());
return "suoritettu";
});
System.out.println("Jälkeen: " + result.isDone()); // true
});
Tämän synkronisen käyttäytymisen avulla käyttöliittymän päivitykset tapahtuvat tapahtumankäsittelijöistä välittömästi ilman tarpeetonta jonotusta.
Taustasäikeistä
Kun kutsutaan taustasäikeestä, tehtävät ovat jonotettuina asynkroniseen suoritukseen:
@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ä tiukassa FIFO-järjestyksessä, säilyttäen operaatioiden järjestyksen myös silloin, kun niitä lähetetään samanaikaisesti useilta säikeiltä. Tämän järjestysvakuutuksen ansiosta käyttöliittymän päivitykset tapahtuvat tarkalleen siinä järjestyksessä, jossa ne lähetetään. 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 perumista, mikä mahdollistaa jonotettujen tehtävien suorittamisen estämisen. Perumalla odottavat tehtävät voit välttää muistivuotoja ja estää pitkäkestoisten toimintojen päivittämistä käyttöliittymässä sen jälkeen, kun niitä ei enää tarvita.
Perusperuutus
PendingResult<Void> result = Environment.runLater(() -> {
updateUI();
});
// Peru, jos ei vielä suoritettu
if (!result.isDone()) {
result.cancel();
}
Useiden päivitysten hallinta
Kun suoritat pitkäkestoisia toimintoja, joissa on usein 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() {
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 perumista
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 navigoinnin aikana), peru 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();
// Peru kaikki odottavat päivitykset estääksesi muistivuodot
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Suunnittelun huomioitavat asiat
-
Kontekstivaatimus: Säikeiden on oltava perineet
Environment
konteksti. Ulkoisten kirjastojen säikeet, järjestelmäajastimet ja staattiset inicialisoijat eivät voi käyttää tätä APIa. -
Muistivuotojen estäminen: Seuraa aina ja peru
PendingResult
-objekteja komponentin elinkaarimenetelmien aikana. Jonotetut lamdat tallentavat viittauksia käyttöliittymäkomponentteihin, mikä estää roskakeräyksen, jos niitä ei peruta. -
FIFO-suoritus: Kaikki tehtävät suoritetaan tiukassa FIFO-järjestyksessä riippumatta tärkeydestä. Prioriteettijärjestelmää ei ole.
-
Peruutuksen rajoitukset: Peruminen estää vain jonotettujen tehtävien suorittamisen. Jo suorittavat tehtävät loppuvat normaalisti.
Täydellinen tapaustutkimus: LongTaskView
Seuraava on täydellinen, tuotantokelpoinen toteutus, joka demonstroi kaikkia parhaita käytäntöjä asynkronisille käyttöliittymän päivityksille:
Tapaustutkimuksen analyysi
Tämä toteutus osoittaa useita kriittisiä malleja:
1. Säietä hallinta
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- Käyttää yksisäiettä resurssien loppumisen estämiseksi
- Luo daemon-säikeitä, jotka eivät estä JVM:n sulkemista
2. Odottavien päivitysten seuraaminen
private final List<PendingResult<?>> pendingUIUpdates = new ArrayList<>();
Jokainen Environment.runLater()
-kutsu seurataan, jotta voidaan mahdollistaa:
- Peruminen, kun käyttäjä napsauttaa peruuta
- Muistivuotojen 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än reaktion peruutukseen
- Siistin lopettamisen silmukasta
- Estää lisäkäyttöliittymän päivityksiä
4. Elinkaaren hallinta
@Override
protected void onDestroy() {
super.onDestroy();
cancelTask(); // Uudelleenkäyttää peruutuslogiikan
currentTask = null;
executor.shutdown();
}
Kriittinen muistivuotojen estämiseksi:
- Peru kaikki odottavat käyttöliittymäpäivitykset
- Keskeytä rinnakkaiset säikeet
- Sammuta säiepooli
5. Käyttöliittymän reagointitestauksen
testButton.onClick(e -> {
int count = clickCount.incrementAndGet();
showToast("Klikkaus #" + count + " - Käyttöliittymä on reagoiva!", Theme.GRAY);
});
Demonstroi, että käyttöliittymässä on vielä reagointia taustatoimintojen aikana.