Asynchronous Updates
Environment.runLater() API tarjoaa mekanismin käyttöliittymän turvalliseen päivittämiseen taustasuorittimilta webforJ-sovelluksissa. Tämä kokeellinen ominaisuus mahdollistaa asynkroniset toiminnot samalla kun säilyttää säikeiden turvallisuuden 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äikeen mallista
webforJ noudattaa tiukkaa säikeen mallia, jossa kaikki käyttöliittymän toiminnot on suoritettava Environment säikeessä. Tämä rajoitus on olemassa seuraavista syistä:
- webforJ API -rajoitukset: Peruksessa oleva webforJ API sitoo säikeen, joka loi istunnon
- Komponentin säikeen affiniteetti: Käyttöliittymäkomponentit ylläpitävät tilaa, joka ei ole säikeiden turvallinen
- Tapahtuman jako: Kaikki käyttöliittymän tapahtumat käsitellään järjestyksessä yhdellä säikeellä
Tämä yksisäikeinen malli estää kilpailutilanteet ja ylläpitää johdonmukaisen tilan kaikille käyttöliittymäkomponenteille, mutta se luo haasteita asynkronisten, pitkäkestoisten laskentatehtävien integroimisessa.
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, joka seuraa tehtävän suorittamista ja tarjoaa pääsyn tulokseen tai mahdollisiin poikkeuksiin.
Säikeen kontekstin perintä
Automaattinen kontekstin perintä on kriittinen ominaisuus Environment.runLater():ssa. Kun Environment-säikeessä suoritetaan lapsisäikeitä, nämä lapset perivät automaattisesti kyvyn käyttää runLater().
Miten perintä toimii
Kaikilla säikeillä, jotka luodaan Environment-säikeen sisällä, on automaattisesti pääsy kyseiseen Environment-konseptiin. 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-konteksti
// Lapsisäikeet perivät kontekstin automaattisesti
executor.submit(() -> {
String data = fetchRemoteData();
// Voi käyttää runLateria, koska konteksti periytyi
Environment.runLater(() -> {
dataLabel.setText(data);
loadingSpinner.setVisible(false);
});
});
}
}
Säikeet ilman kontekstia
Säikeet, jotka luodaan Environment-konteksti ulkopuolella, eivät voi käyttää runLater() ja heitetään IllegalStateException:
// Statistinen inicialisoija - ei Environment-konteksti
static {
new Thread(() -> {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}).start();
}
// Järjestelmän ajastin säikeet - ei Environment-konteksti
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
Environment.runLater(() -> {}); // Heittää IllegalStateException
}
}, 1000);
// Ulkoisen kirjaston säikeet - ei Environment-konteksti
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ästä
Kun kutsutaan suoraan 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()); // true
});
Tämän synkronisen käyttäytymisen avulla käyttöliittymän päivitykset tapahtuvat heti tapahtumakäsittelijöistä, eivätkä ne vie turhaan jonoa.
Taustasäikeistä
Kun kutsutaan taustasäikeestä, tehtävät jonotetaan asynkroniseen suorittamiseen:
@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ässä
// Tehtävä on jonotettu ja suoritetaan asynkronisesti
});
}
webforJ käsittelee taustasäikeistä lähetettyjä tehtäviä tiukassa FIFO-järjestyksessä, säilyttäen operaatioiden sekvenssin, vaikka ne on lähetetty useista säikeistä samanaikaisesti. Tämän järjestyksen takia käyttöliittymän päivitykset suoritetaan täsmälleen siinä järjestyksessä, jossa ne on lähetetty. Jos siis 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än epäjärjestyksen.
Tehtävän peruutus
PendingResult, joka palautetaan Environment.runLater() -menetelmästä, tukee peruutusta, jolloin voit estää jonotettujen tehtävien suorittamisen. Peruuttamalla odottavat tehtävät voit välttää muistivuodot ja estää pitkäaikaiset toiminnot päivittämästä käyttöliittymää sen jälkeen, 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
Kun suoritat pitkäkestoisia toimintoja, joissa on usein käyttöliittymän 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 mahdollisia peruutuksia
pendingUpdates.add(update);
Thread.sleep(100);
}
});
}
public void cancelTask() {
isCancelled = true;
// Peruuta 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 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 muistivuotojen estämiseksi
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
Suunnittelu näkökohtia
-
Kontekstivaatimus: Säikeillä on oltava peritty
Environment-konteksti. Ulkoiset kirjastosäikeet, järjestelmäajastimet ja staattiset inicialisoijat eivät voi käyttää tätä API:a. -
Muistivuotojen estäminen: Seuraa aina ja peruuta
PendingResult-objekteja komponentin elinkaarimetodissa. Jonotetut lambdat vangitsevat viittauksia käyttöliittymäkomponentteihin, estäen roskat keräämisen, ellei niitä peruuteta. -
FIFO-suoritus: Kaikki tehtävät suoritetaan tiukassa FIFO-järjestyksessä merkityksestä riippumatta. Prioriteettijärjestelmää ei ole.
-
Peruutusrajoitukset: Peruuttaminen estää vain jonotettujen tehtävien suorittamisen. Jo suorittavat tehtävät suoritetaan normaalisti.
Täydellinen tapaustutkimus: LongTaskView
Seuraava on täydellinen, tuotantovalmiin toteutus, joka osoittaa kaikki parhaat käytännöt asynkronisessa käyttöliittymän päivityksessä:
Tapaustutkimuksen analyysi
Tämä toteutus osoittaa useita kriittisiä malleja:
1. Säiettä hallinnointi
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- Käyttää yksittäistä säidehtoa estämään resurssien loppumisen
- 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 mahdollistamaan:
- Peruuttaminen, kun käyttäjä napsauttaa peruutusta
- Muistivuotojen estäminen
onDestroy() - Oikea puhdistus komponentin elinkaaren aikana
3. Yhteistyöperuutus
private volatile boolean isCancelled = false;
Taustasäie tarkistaa tämän lipun jokaisessa iteraatiossa, mahdollistaen:
- Välittömän reagoinnin peruutukseen
- Puhdasta poistumista silmukasta
- Lisäkäyttöliittymäpäivitysten estämistä
4. Elinkaaren hallinta
@Override
protected void onDestroy() {
super.onDestroy();
cancelTask(); // Uudelleenkäyttää peruutuslogiikkaa
currentTask = null;
executor.shutdown();
}
Kriittinen muistivuotojen estämiseksi:
- Peruuta kaikki odottavat käyttöliittymäpäivitykset
- Keskeytä käynnissä olevat säikeet
- Sulje säieterä
5. Käyttöliittymän reagointikyvyn testaaminen
testButton.onClick(e -> {
int count = clickCount.incrementAndGet();
showToast("Napsautus #" + count + " - käyttöliittymä on responsiivinen!", Theme.GRAY);
});
Todistaa, että käyttöliittymä säie pysyy responsiivisena taustatoimintojen aikana.