Siirry pääsisältöön

Background Jobs

Avaa ChatGPT:ssä

Kun käyttäjät napsauttavat painiketta raportin luomiseksi tai tietojen käsittelemiseksi, he odottavat käyttöliittymän pysyvän reaktiivisena. Edistymispalkkien tulisi animoitua, painikkeet reagoida hover-tilassa, eikä sovelluksen tulisi jäätyä. Springin @Async-annotaatio mahdollistaa tämän siirtämällä pitkät toiminnot taustasäikeisiin.

webforJ valvoo säikeiden turvallisuutta käyttöliittymän komponenteille - kaikki päivitykset on tehtävä käyttöliittymän säikeessä. Tämä luo haasteen: kuinka taustatehtävät voivat päivittää edistymispalkkeja tai näyttää tuloksia? Vastaus on Environment.runLater(), joka siirtää käyttöliittymän päivitykset turvallisesti Springin taustasäikeistä webforJ:n käyttöliittymän säikeeseen.

Asynkronisen suorittamisen mahdollistaminen

Springin asynkronisen metodin suorittaminen vaatii eksplisiittistä konfigurointia. Ilman sitä, @Async-annotaatiolla varustetut metodit suoritetaan synkronisesti, mikä kumoaa niiden tarkoituksen.

Lisää @EnableAsync Spring Boot -sovelluksesi luokkaan:

@SpringBootApplication
@EnableAsync
@Routify(packages = { "com.example.views" })
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@EnableAsync -annotaatio aktivoi Springin infrastruktuurin @Async-metodien tunnistamiseksi ja niiden suorittamiseksi taustasäikeissä.

Springin asynkroninen opas

Nopea johdanto Springin @Async-annotaatioon ja perustason käyttömallit löytyvät täältä: Luodaan asynkronisia metodeja.

Asynkronisten palvelujen luominen

@Service-annotaatiolla varustetuilla palveluilla voi olla metodeja, jotka on merkitty @Async:lla ja joita suoritetaan taustasäikeissä. Nämä metodit palauttavat yleensä CompletableFuture-tyyppisiä arvoja, jotta voimme käsitellä valmistumista ja peruutusta oikein:

@Service
public class BackgroundService {

@Async
public CompletableFuture<String> performLongRunningTask(Consumer<Integer> progressCallback) {
try {
for (int i = 0; i <= 10; i++) {
// Raportoi edistyminen
int progress = i * 10;
if (progressCallback != null) {
progressCallback.accept(progress);
}

// Simuloi työtä
Thread.sleep(500);
}

return CompletableFuture.completedFuture(
"Tehtävä suoritettu onnistuneesti taustapalvelimelta!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(e);
}
}
}

Tämä palvelu hyväksyy edistymiskutsun (Consumer<Integer>), jota kutsutaan taustasäikeestä. Kutsumalli mahdollistaa palvelun raportoinnin edistyksestä ilman tietoa käyttöliittymän komponenteista.

Metodi simuloi 5 sekunnin toimintoa, jossa on 10 edistymispäivitystä. Tuotannossa tämä olisi todellista työtä, kuten tietokantakyselyjä tai tiedostojen käsittelyä. Poikkeusten käsittely palauttaa keskeytysstatuksen tukeakseen asianmukaista tehtävän peruutusta, kun cancel(true) kutsutaan.

Taustatehtävien käyttäminen näkymissä

Näkymä vastaanottaa taustapalvelun konstruktori-injektion avulla:

@Route("/")
public class HelloWorldView extends Composite<FlexLayout> {
private Button asyncBtn = new Button("Aloita taustatehtävä");
private ProgressBar progressBar = new ProgressBar();
private CompletableFuture<String> currentTask;

public HelloWorldView(BackgroundService backgroundService) {
// Palvelu injektoidaan Springin avulla
asyncBtn.addClickListener(e -> {
currentTask = backgroundService.performLongRunningTask(progress -> {
Environment.runLater(() -> {
progressBar.setValue(progress);
});
});
});
}
}

Spring injektoi BackgroundService-palvelun näkymän konstruktorin kautta, aivan kuten kaikki muutkin Spring-beanit. Näkymä käyttää tätä palvelua taustatehtävien käynnistämiseen. Keskeinen käsite: palvelusta tulevat kutsut suoritetaan taustasäikeissä, joten kaikki käyttöliittymän päivitykset näiden kutsujen sisällä on suoritettava Environment.runLater() -menetelmällä, jotta suoritus siirtyy käyttöliittymän säikeeseen.

Valmistumisen käsittely vaatii samaa huolellista säiemanagerointia:

currentTask.whenComplete((result, error) -> {
Environment.runLater(() -> {
asyncBtn.setEnabled(true);
progressBar.setVisible(false);
if (error != null) {
Toast.show("Tehtävä epäonnistui: " + error.getMessage(), Theme.DANGER);
} else {
Toast.show(result, Theme.SUCCESS);
}
});
});

whenComplete-kutsu suoritetaan myös taustasäikeessä. Jokainen käyttöliittymätoiminto - kuten painikkeen aktivointi, edistymispalkin piilottaminen, toast-viestien näyttäminen - on pakattava Environment.runLater() -menetelmään. Ilman tätä pakkausta webforJ heittää poikkeuksia, koska taustasäikeet eivät voi käyttää käyttöliittymäkomponentteja.

Säieturvallisuus

Jokainen käyttöliittymän päivitys taustasäikeestä on pakattava Environment.runLater() -menetelmään. Tälle säännölle ei ole poikkeuksia. Suora komponenttipääsy @Async-metodeista epäonnistuu aina.

Lisätietoja säieturvallisuudesta

Yksityiskohtaiset tiedot webforJ:n säiemallista, suorituskäyttäytymisestä ja siitä, mitkä toiminnot vaativat Environment.runLater(), löydät täältä: Asynkroniset päivitykset.

Tehtävän peruutus ja siivous

Oikea elinkaaren hallinta estää muistivuodot ja ei-toivotut käyttöliittymän päivitykset. Näkymä tallentaa CompletableFuture-viittauksen:

private CompletableFuture<String> currentTask;

Kun näkymä tuhoutuu, se peruuttaa kaikki käynnissä olevat tehtävät:

@Override
protected void onDestroy() {
// Peruuta tehtävä, jos näkymä tuhoutuu
if (currentTask != null && !currentTask.isDone()) {
currentTask.cancel(true);
}
}

cancel(true)-parametri on ratkaiseva. Se keskeyttää taustasäikeen, jolloin estävät toiminnot, kuten Thread.sleep(), heittävät InterruptedException-poikkeuksen. Tämä mahdollistaa välittömän tehtävän lopettamisen. Ilman keskeytysmerkkiä (cancel(false)), tehtävä jatkaisi suorittamista, kunnes se tarkistaa nimenomaisesti peruutuksen.

Tämä siivous estää useita ongelmia:

  • Taustasäikeet kuluttavat edelleen resursseja näkymän häviämisen jälkeen
  • Käyttöliittymän päivitykset yrittävät muokata tuhottuja komponentteja
  • Muistivuodot, jotka johtuvat kutsuista, jotka pitävät viittauksia käyttöliittymän komponentteihin