Una delle maggiori difficoltà di un sistema complesso è restare attivo durante l'esecuzione delle attività. Questa difficoltà è maggiore in un sistema estensibile, quando i componenti che non sono stati concepiti per essere eseguiti contemporaneamente condividono le stesse risorse. Il pacchetto org.eclipse.core.runtime.jobs affronta questa difficoltà fornendo un'infrastruttura per la pianificazione, l'esecuzione e la gestione di operazioni contemporanee. Questa infrastruttura si basa sull'utilizzo dei lavori per rappresentare un'unità di lavoro che può essere eseguita in modo asincrono.
class TrivialJob extends Job { public TrivialJob() { super("Trivial Job"); } public IStatus run(IProgressMonitor monitor) { System.out.println("This is a job"); return Status.OK_STATUS; } }Il lavoro viene creato e pianificato nel seguente frammento:
TrivialJob job = new TrivialJob(); System.out.println("About to schedule a job"); job.schedule(); System.out.println("Finished scheduling a job");L'output di questo programma dipende dalla durata. Ciò significa che non esiste alcun modo per verificare che il metodo run del lavoro venga eseguito in relazione al thread che ha creato e pianificato il lavoro. L'output sarà:
About to schedule a job This is a job Finished scheduling a jobo:
About to schedule a job Finished scheduling a job This is a job
Se, prima di continuare, si desidera essere certi che un lavoro sia stato completato, è possibile utilizzare il metodo join(). Questo metodo blocca il mittente fino al completamento del lavoro o fino all'interruzione del thread di chiamata. Il frammento viene scritto di nuovo, in modo più dettagliato:
TrivialJob job = new TrivialJob(); System.out.println("About to schedule a job"); job.schedule(); job.join(); if (job.getResult().isOk()) System.out.println("Job completed with success"); else System.out.println("Job did not complete successfully");Se la chiamata join() non viene interrotta, questo metodo restituisce il seguente risultato:
About to schedule a job This is a job Job completed with success
Naturalmente, non è utile in genere unire un lavoro subito dopo la sua pianificazione, in quanto non si ottiene alcuna simultaneità. In questo caso, è possibile eseguire il lavoro dal metodo run del lavoro stesso direttamente nel thread di chiamata. Di seguito vengono riportati alcuni esempi in cui l'uso dell'unione ha maggior senso.
Anche nell'ultimo frammento viene utilizzato il risultato del lavoro. Il risultato è l'oggetto IStatus restituito dal metodo run() del lavoro. È possibile utilizzare questo risultato per trasferire qualsiasi oggetto necessario dal metodo run del lavoro. Il risultato può anche essere utilizzato per indicare un errore (restituendo un IStatus con gravità IStatus.ERROR) o un annullamento (IStatus.CANCEL).
È stato illustrato come pianificare un lavoro e attendere che il lavoro venga completato, ma esistono molte altre attività interessanti che è possibile eseguire con i lavori. Se si pianifica un lavoro ma si decide poi che non è più necessario, il lavoro può essere arrestato utilizzando il metodo cancel(). Se l'esecuzione del lavoro non è stata ancora avviata al momento dell'annullamento, il lavoro viene subito eliminato e non viene eseguito. Se, d'altro canto, l'esecuzione del lavoro è già iniziata, la risposta all'annullamento dipende dal lavoro. Quando si tenta di annullare un lavoro, si attende il momento appropriato utilizzando il metodo join(). Di seguito viene riportato un idioma comune per l'annullamento di un lavoro e l'attesa che un lavoro venga completato prima di continuare:
if (!job.cancel()) job.join();
Se l'annullamento non ha effetto immediatamente, il metodo cancel() restituisce un valore false e il mittente utilizza il metodo join() per attendere che il lavoro venga annullato correttamente.
Meno drastico dell'annullamento è il metodo sleep(). Se l'esecuzione del lavoro non è stata ancora avviata, questo metodo consente di porre il lavoro in attesa per un periodo di tempo indeterminato. Il lavoro verrà ancora ricordato dalla piattaforma e con una chiamata wakeUp() verrà aggiunto alla coda in attesa in cui verrà eventualmente eseguito.
Un lavoro può presentare stati diversi. Lo stato può essere modificato mediante un API, ad esempio cancel() e sleep() o essere modificato quando la piattaforma esegue e completa il lavoro. I lavori possono essere spostati tra i seguenti stati:
Un lavoro può essere interrotto solo se è attualmente nello stato WAITING. Se si riprende un lavoro interrotto, il lavoro ritorna nello stato WAITING. Se si annulla un lavoro, il lavoro ritorna nello stato NONE.
Se il plugin utilizzato deve conoscere lo stato di un determinato lavoro, è possibile registrare un listener di modifica del lavoro, notificato nel momento in cui il lavoro si sposta nel suo ciclo di vita. Questa strategia è utile per visualizzare l'avanzamento o il prospetto di un lavoro.
Il metodo Job addJobChangeListener può essere utilizzato per registrare un listener su un determinato lavoro. IJobChangeListener definisce il protocollo per rispondere allo modifiche dello stato in un lavoro:
In tutti questi casi, il listener viene fornito con un protocollo IJobChangeEvent che specifica il lavoro sottoposto alla modifica dello stato e lo stato al momento del completamento del lavoro (se il lavoro è stato eseguito).
Nota: i lavori definiscono anche il metodo getState() per ottenere lo stato (relativamente) corrente di un lavoro. Tuttavia, questo risultato non è sempre affidabile, dal momento che i lavori vengono eseguiti in thread diversi e il loro stato può essere di nuovo modificato con una nuova chiamata. I listener di modifica dei lavori rappresentano il meccanismo consigliato per individuare le modifiche dello stato di un lavoro.
IJobManager definisce il protocollo per la gestione di tutti i lavori del sistema. I plugin che mostrano un avanzamento o gestiscono l'infrastruttura dei lavori possono utilizzare IJobManager per eseguire attività quali la sospensione di tutti i lavori del sistema, la ricerca del lavoro da eseguire o la ricezione del feedback di avanzamento su un determinato lavoro. Il gestore lavori della piattaforma può essere richiamato utilizzando l'API della Piattaforma:
IJobManager jobMan = Platform.getJobManager();
I plugin interessati allo stato di tutti i lavori del sistema possono registrare un listener di modifica dei lavori nel gestore lavori anziché registrare i listener su più lavori singoli.
Talvolta è più semplice per un plugin gestire un gruppo di lavori correlati come una singola unità. Ciò è possibile utilizzando le famiglie di lavori. Un lavoro dichiara di appartenere ad una determinata famiglia sostituendo il metodo belongsTo:
public static final String MY_FAMILY = "myJobFamily"; ... class FamilyJob extends Job { ... public boolean belongsTo(Object family) { return family == MY_FAMILY; } }Il protocollo IJobManager può essere utilizzato per annullare, unire, interrompere o individuare tutti i lavori di una famiglia:
IJobManager jobMan = Platform.getJobManager(); jobMan.cancel(MY_FAMILY); jobMan.join(MY_FAMILY, null);
Poiché le famiglie di lavori sono rappresentate utilizzando oggetti arbitrari, è possibile memorizzare uno stato interessante della famiglia di lavori, mentre i lavori possono generare dinamicamente gli oggetti della famiglia in base alle necessità. È importante utilizzare oggetti della famiglia univoci, per evitare l'interazione accidentale con le famiglie create da altri plugin.
Le famiglie rappresentano un modo comodo per individuare i gruppi di lavori. Il metodo IJobManager.find(Object family) può essere utilizzato per individuare le istanze di tutti i lavori in esecuzione, in attesa e interrotti in un determinato momento.