Asynchronous Updates
Environment.runLater() -API tarjoaa mekanismin käyttöliittymän turvalliseen päivittämiseen taustasäikeistä webforJ-sovelluksissa. Tämä kokeellinen ominaisuus mahdollistaa asynkroniset toiminnot samalla pitäen käyttöliittymän muokkausten säikeiturvallisina.
Ymmärtäminen säikeen mallista
webforJ noudattaa tiukkaa säikeiden mallia, jossa kaikki käyttöliittymätoiminnot on suoritettava Environment-säikeessä. Tämä rajoite johtuu siitä, että:
- webforJ API -rajoitukset: Taustalla oleva webforJ API sidotaan säikeeseen, joka loi istunnon.
- Komponenttien säikesuhteet: Käyttöliittymäkomponentit ylläpitävät tilaa, joka ei ole säikeen turvallista.
- Tapahtumien käsittely: Kaikki käyttöliittymä tapahtumat käsitellään peräkkäin yhdellä säikeellä.
Tämä yksisäikeinen malli estää kilpailutilanteet ja ylläpitää johdonmukaista tilaa kaikille käyttöliittymäkomponenteille, mutta luo haasteita integroitaessa asynkronisia, pitkiä laskentatehtäviä.
RunLater -API
Environment.runLater() -API tarjoaa kaksi menetelmää käyttöliittymän päivitysten aikatauluttamiseen:
// 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 antaa pääsyn tulokseen tai mahdollisiin poikkeuksiin, jotka tapahtuivat.
Säikeen kontekstiin periytyminen
Automaattinen kontekstitietoisuus on kriittinen ominaisuus Environment.runLater() -metodissa. Kun Environment -säikeessä toimiva säie luo lapsisäikeitä, nämä lapset perivät automaattisesti kyvyn käyttää runLater() -metodia.
Kuinka periytyminen toimii
Mikä tahansa säie, joka on luotu Environment -säikeessä, pääsee automaattisesti siihen Environment-kontekstiin. Tämä periytyminen tapahtuu automaattisesti, joten konteksin siirtämistä tai mitään konfigurointia ei tarvita.
@Route
public class DataView extends Composite<Div> {
private final ExecutorService executor = Executors.newCachedThreadPool();
public DataView() {
// Tämä säie saa Environment-kontekstin
// Lapsisäikeet perivät kontekstin automaattisesti
executor.submit(() -> {
String data = fetchRemoteData();
// Voi käyttää runLateria, koska konteksti on peritty
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 ne heittävät IllegalStateException -poikkeuksen:
// Staattinen alustaja - ei Environment-kontekstia
static {
new Thread(() -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}).start();
}
// Järjestelmäajurit - 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() -metodin suoritus käyttäytyminen riippuu siitä, mikä säie sitä kutsuu:
Käyttöliittymästä
Kun kutsutaan Environment -säikeeltä itseltää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 avulla käytt öliittymän päivitykset tapahtuvat heti tapahtumakäsittelijöistä, eikä aiheuta turhaa jonottamista.
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 jonossa ja suoritetaan asynkronisesti
});
}
webforJ käsittelee taustasäikeistä lähetetyt tehtävät tiukassa FIFO-järjestyksessä, säilyttäen operaatioiden järjestyksen, vaikka ne olisi lähetetty useilta säikeiltä samanaikaisesti. Tämän järjestyksen takuurajaamisella käyttöliittymän päivitykset toteutuvat täsmälleen siinä järjestyksessä, jossa ne ovat 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äjohdonmukaisuudet käyttöliittymässä.
Tehtävän peruuttaminen
PendingResult -objekti, joka palautetaan Environment.runLater() -metodista, tukee peruuttamista, jolloin voit estää jonossa olevien tehtävien suorittamisen. Peruuttamalla odottavat tehtävät, voit välttää muistivuotoja ja estää pitkäkestoisia operaatioita päivittämästä käyttöliittymää, kun niitä ei enää tarvita.
Perusperuutus
PendingResult<Void> result = Environment.runLater(() -> {
updateUI();
});
// Peruuta, jos ei ole vielä suoritettu
if (!result.isDone()) {
result.cancel();
}
Useiden päivitysten hallinta
Pitkäkestoisia toimintoja, joissa on tiheitä käyttöliittymäpäivityksiä, suorittaessasi 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 peruuttamista
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 (esim. navigoinnin aikana), peruuta kaikki odottavat päivitykset estääksesi muistivuotoja:
@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();
}
}
Suunnittelupohdinnat
-
Kontekstivaatimus: Säikeiden on oltava perineet
Environment-konteksti. Ulkoiset kirjastosäikeet, järjestelmäajurit ja staattiset alustajat eivät voi käyttää tätä APIa. -
Muistivuotojen estäminen: Seuraa aina
PendingResult-objekteja komponentin elinkaarimenetelmissä. Jonotetut lambdat kaappaavat viittauksia käyttöliittymäkomponentteihin, estäen roskakeräyksen, jos niitä ei peruuteta. -
FIFO-suoritus: Kaikki tehtävät suoritetaan tiukassa FIFO-järjestyksessä merkityksestä riippumatta. Prioriteettijärjestelmää ei ole.
-
Peruuttamisen rajoitukset: Peruuttaminen estää vain jonossa olevien tehtävien suorittamisen. Jo suorittavat tehtävät valmistuvat normaalisti.
Täydellinen tapaustutkimus: LongTaskView
Seuraava on täydellinen, tuotantovalmiiksi toteutus, joka osoittaa kaikki parhaat käytännöt asynkronisille käyttöliittymäpäivityksille:
Tapaustutkimuksen analyysi
Tämä toteutus osoittaa useita kriittisiä malleja:
1. Säiettä hallinta
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- Käyttää yhden säikeen suorittajaa estämään resurssien uupumista
- Luo daemon-säikeitä, jotka eivät estä JVM:n sammutusta
2. Odottavien päivitysten seuraaminen
private final List<PendingResult<?>> pendingUIUpdates = new ArrayList<>();
Jokainen Environment.runLater() -kutsu seurataan, mikä mahdollistaa:
- Peruuttamisen, kun käyttäjä napsauttaa peruuta
- Muistivuotojen estämisen
onDestroy():ssa - Oikean puhdistuksen komponentin elinkaaren aikana
3. Yhteistyöperuuttaminen
private volatile boolean isCancelled = false;
Taustasäie tarkistaa tämän lipun jokaisessa iteraatiossa, mikä mahdollistaa:
- Välittömän reagoinnin peruuttamiseen
- Siistin poistumisen silmukasta
- Lisäämällä käyttöliittymän päivityksiä
4. Elinkaaren hallinta
@Override
protected void onDestroy() {
super.onDestroy();
cancelTask(); // Uudelleenkäyttää peruuttamislogiikan
currentTask = null;
executor.shutdown();
}
Kriittinen muistivuotojen estämiseksi:
- Peruuta kaikki odottavat käyttöliittymäpäivitykset
- Keskeytä käynnissä olevat säikeet
- Sammuta suorittaja
5. Käyttöliittymän reagointikyvyn testaus
testButton.onClick(e -> {
int count = clickCount.incrementAndGet();
showToast("Klikkaus #" + count + " - käyttöliittymä on reagoiva!", Theme.GRAY);
});
Todistaa, että käyttöliittymän säie pysyy reagoivana taustatoimintojen aikana.