Infrastruktura souběžnosti

Jedním z největších problémů u složitého systému je zajistit, aby dokázal odpovídat i během provádění úloh. Tento problém je ještě větší u rozšiřitelného systému, pokud komponenty, které nebyly navrženy pro souběžné spouštění, sdílejí tytéž prostředky. Balíček org.eclipse.core.runtime.jobs tento problém řeší tím, že poskytuje infrastrukturu pro plánování, provádění a správu souběžně prováděných operací. Tato infrastruktura je založena na úlohách, které představují jednotky práce, jež mohou probíhat asynchronně.

Úlohy

Třída Job představuje jednotku asynchronní práce, probíhající souběžně s jinými úlohami. K provedení úlohy modul plug-in vytvoří úlohu a pak ji naplánuje. Po naplánování je úloha přidána do fronty úloh spravované platformou. Ke správě všech nevyřízených úloh platforma používá vlákno (proces) plánování na pozadí. Jakmile se spuštěná úloha ukončí, je odebrána z fronty a platforma rozhodne, která úloha se má spustit jako další. Když se úloha aktivuje, platforma vyvolá její metodu run(). Úlohy si můžeme nejlépe demonstrovat pomocí jednoduchého příkladu:
   class TrivialJob extends Job {
      public TrivialJob() {
         super("Triviální úloha");
      }
         public IStatus run(IProgressMonitor monitor) {
         System.out.println("Toto je úloha");
            return Status.OK_STATUS;
      }
   }
Úloha se vytvoří a naplánuje v následujícím úseku kódu:
   TrivialJob job = new TrivialJob();
   System.out.println("Chystám se naplánovat úlohu");
   job.schedule();
   System.out.println("Naplánování úlohy dokončeno");
Výstup tohoto programu je závislý na časování. To znamená, že neexistuje jistota, kdy se metoda run úlohy spustí ve vztahu k vláknu (procesu), které úlohu vytvořilo a naplánovalo. Výstup bude buď:
   Chystám se naplánovat úlohu
   Toto je úloha
   Naplánování úlohy dokončeno
nebo:
   Chystám se naplánovat úlohu
   Naplánování úlohy dokončeno
   Toto je úloha

Pokud si chcete být jisti, že se úloha dokončila, než budete pokračovat, můžete použít metodu join(). Tato metoda zablokuje volajícího do doby, dokud úloha neskončí nebo dokud nebude vyvolávající vlákno (proces) přerušeno. Přepišme si nyní výše uvedený úsek kódu tak, aby byl determinističtější:

      TrivialJob job = new TrivialJob();
   System.out.println("Chystám se naplánovat úlohu");
   job.schedule();
      job.join();
   if (job.getResult().isOk())
      System.out.println("Úloha byla úspěšně dokončena");
    else
      System.out.println("Úloha nebyla úspěšně dokončena");
Za předpokladu, že volání join() nebude přerušeno, poskytne tato metoda zaručeně následující výsledek:
   Chystám se naplánovat úlohu
   Toto je úloha
   Úloha byla úspěšně dokončena

Obecně však není samozřejmě vhodné sloučit úlohu pomocí metody join ihned po jejím naplánování, protože tím nezískáte žádnou souběžnost. V takovém případě byste stejně dobře mohli provést práci z metody run dané úlohy přímo ve vyvolávajícím vláknu (procesu). Některé příklady si uvedeme později nebo v situacích, kde je použití metody join smysluplnější.

Posledně uvedený úsek kódu také využívá výsledek úlohy. Výsledek je objekt IStatus, který vrátí metoda run() dané úlohy. Tento výsledek můžete použít k předání jakýchkoli potřebných objektů zpět z metody run dané úlohy. Výsledek lze také využít k indikaci selhání (tím, že se vrátí IStatus s kódem závažnosti IStatus.ERROR) nebo zrušení (IStatus.CANCEL).

Obecné operace s úlohami

Už jsme si ukázali, jak úlohu naplánovat a počkat, až proběhne, avšak s úlohami se dá provádět mnohem více zajímavých věcí. Pokud naplánujete úlohu, ale poté se rozhodnete, že již není zapotřebí, je možno ji zastavit pomocí metody cancel(). Pokud v okamžiku zrušení úloha ještě nezačala probíhat, bude okamžitě odstraněna a nespustí se. Pokud však již probíhat začala, závisí jen na ní samotné, zda chce na zrušení reagovat. Pokud zkoušíte úlohu zrušit, je vhodné počkat na její zrušení za pomoci metody join(). Zde je ukázka obecného způsobu, jak zrušit úlohu a před dalším postupem počkat, dokud neskončí:

      if (!job.cancel())
      job.join();

Pokud se zrušení neprojeví okamžitě, potom metoda cancel() vrátí hodnotu false a volající za použití metody join() počká, až se úloha úspěšně zruší.

O něco méně drastickým řešením než zrušení je metoda sleep(). I zde platí, že pokud úloha ještě nezačala probíhat, způsobí tato metoda její pozastavení na neomezenou dobu. Platforma si bude úlohu i nadále pamatovat a pomocí volání wakeUp() ji lze přidat do fronty čekajících úloh, kde bude nakonec provedena.

Stavy úloh

Během svého životního cyklu prochází úloha několika různými stavy. Nejenže s ní lze manipulovat prostřednictvím API, např. cancel() a sleep(), ale její stav se také mění tak, jak ji platforma spouští a dokončuje. Úlohy mohou procházet následujícími stavy:

Úlohu lze uspat pouze tehdy, je-li momentálně ve stavu WAITING. Probuzením se spící úloha vrátí do stavu WAITING. Zrušení vrátí úlohu do stavu NONE.

Pokud váš modul plug-in potřebuje znát stav určité úlohy, může zaregistrovat listener změn úlohy, který je upozorňován při přechodu úlohy z jednoho stavu do jiného během jejího životního cyklu. To je užitečné pro zobrazování průběhu nebo jiného hlášení o úloze.

Listenery změn úlohy

K zaregistrování listeneru pro určitou úlohu je možno použít metoduJob addJobChangeListener. IJobChangeListener definuje protokol reagování na změny stavu úlohy:

Ve všech těchto případech je poskytnut listener s metodou IJobChangeEvent, která udává, jaká úloha prochází změnou stavu a jaký je její stav po dokončení této změny (pokud se provede).

Poznámka: Úlohy také definují metodu getState() pro získání (relativně) aktuálního stavu úlohy. Její výsledek však není vždy spolehlivý, protože úlohy se spouští v různých vláknech (procesech) a jejich stav se může změnit do doby, než volání vrátí výsledek. Listenery změn úlohy jsou doporučeným mechanizmem pro zjišťování změn stavu úloh.

Správce úloh

IJobManager definuje protokol pro práci se všemi úlohami v systému. Moduly plug-in, které vykazují postup nebo jinak pracují s infrastrukturou úloh, mohou IJobManager používat k provádění takových úkonů, jako je pozastavení všech úloh v systému, zjištění, která úloha právě běží, nebo získávání zpětné vazby o postupu určité úlohy. Správce úloh platformy je možno získat za použití API Platforma:

      IJobManager jobMan = Platform.getJobManager();

Moduly plug-in, které zajímá stav všech úloh v systému, mohou listener změn úlohy místo na všechny jednotlivé úlohy zaregistrovat na správce úloh.

Rodiny úloh

Někdy je pro modul plug-in snadnější pracovat se skupinou navzájem příbuzných úloh jako s celkem. Toho lze dosáhnout použitím rodin úloh. To, že patří do určité rodiny, úloha deklaruje dočasným přepsáním metody belongsTo:

   public static final String MY_FAMILY = "myJobFamily";
   ...
   class FamilyJob extends Job {
      ...
      public boolean belongsTo(Object family) {
         return family == MY_FAMILY;
      }
   }
Protokol správce úloh IJobManager lze použít ke zrušení, sloučení, uspání nebo vyhledání všech úloh patřících do určité rodiny:
   IJobManager jobMan = Platform.getJobManager();
   jobMan.cancel(MY_FAMILY);
   jobMan.join(MY_FAMILY, null);

Jelikož se rodiny úloh reprezentují za pomoci libovolných objektů, můžete uložit zajímavý stav rodiny objektů jako takové a úlohy mohou dynamicky sestavovat objekty rodiny podle potřeby. Je důležité používat objekty rodiny, které jsou dostatečně jedinečné, aby nedocházelo k náhodné interakci s rodinami vytvořenými jinými moduly plug-in.

Rodiny jsou také pohodlným způsobem vyhledávání skupin úloh. Za pomoci metody IJobManager.find(Object family) lze najít instance všech spuštěných, čekajících a spících úloh v jakémkoli daném okamžiku.