Υποδομή ταυτοχρονισμού

Μια από τις σημαντικότερες προκλήσεις ενός σύνθετου συστήματος είναι να παραμένει αλληλεπιδραστικό κατά την εκτέλεση των εργασιών. Αυτή η πρόκληση είναι ακόμα μεγαλύτερη σε ένα σύστημα με δυνατότητα επέκτασης, όταν τα συστατικά στοιχεία τα οποία σχεδιάστηκαν ώστε να εκτελούνται μαζί κάνουν κοινή χρήση των ίδιων πόρων. Το πακέτο org.eclipse.core.runtime.jobs απαντά σε αυτή την πρόκληση παρέχοντας υποδομή για τον προγραμματισμό, την εκτέλεση και τη διαχείριση λειτουργιών ταυτοχρονισμένης εκτέλεσης. Η εν λόγω υποδομή βασίζεται στη χρήση εργασιών για την απεικόνιση μιας μονάδας εργασίας η οποία μπορεί να εκτελεστεί χωρίς συγχρονισμό.

Εργασίες

Η κλάση Εργασία αντιπροσωπεύει μια μονάδα ασύγχρονης εργασίας που εκτελείται ταυτοχρονισμένα με άλλες εργασίες. Για την εκτέλεση μιας εργασίας, η πρόσθετη λειτουργία δημιουργεί την εργασία και στη συνέχεια την προγραμματίζει. Αφού ολοκληρώσει τον προγραμματισμό, προστίθεται σε μια ουρά εργασιών την οποία διαχειρίζεται η πλατφόρμα. Η πλατφόρμα χρησιμοποιεί ένα νήμα προγραμματισμού παρασκηνίου για να διαχειριστεί όλες τις εργασίες σε εκκρεμότητα. Με την ολοκλήρωση της εκτελούμενης εργασίας, αυτή αφαιρείται από την ουρά και η πλατφόρμα αποφασίζει ποια εργασία θα εκτελέσει στη συνέχεια. Όταν μια οποιαδήποτε εργασία γίνει ενεργή, η πλατφόρμα καλεί τη σχετική με αυτήν μέθοδο run(). Οι εργασίες παρουσιάζονται καλύτερα με ένα απλό παράδειγμα:
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;
      }
   }
Η εργασία δημιουργείται και προγραμματίζεται στο ακόλουθο τμήμα κώδικα:
TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   System.out.println("Finished scheduling a job");
Η έξοδος αυτού του προγράμματος εξαρτάται από το χρόνο. Δηλαδή, δεν υπάρχει τρόπος να βεβαιωθείτε ότι η μέθοδος run της εργασίας θα εκτελεστεί σε σχέση με το νήμα που δημιούργησε και προγραμμάτισε την εργασία. Τα δεδομένα εξόδου θα έχουν οποιαδήποτε από τις εξής μορφές:
   About to schedule a job
   This is a job
   Finished scheduling a job
ή:
   About to schedule a job
   Finished scheduling a job
   This is a job

Αν θέλετε, πριν συνεχίσετε, να βεβαιωθείτε ότι η εργασία έχει ολοκληρωθεί, μπορείτε να χρησιμοποιήσετε τη μέθοδο join(). Η μέθοδος αυτή θα μπλοκάρει το στοιχείο υποβολής της κλήσης μέχρι την ολοκλήρωση της εργασίας, ή μέχρι τη διακοπή του νήματος κλήσης. Ας γράψουμε πάλι το παραπάνω τμήμα κώδικα με πιο προβλεπόμενο τρόπο:

   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");
Με την προϋπόθεση ότι η κλήση της μεθόδους join() δεν διακόπτεται, αυτή η μέθοδος επιστρέφει το ακόλουθο αποτέλεσμα:
   About to schedule a job
   This is a job
   Job completed with success

Φυσικά, η συμμετοχή σε μια εργασία αμέσως μετά τον προγραμματισμό της δεν συνιστάται συνήθως, αφού με την ενέργεια αυτή δεν αποκτάτε ταυτοχρονισμό. Σε αυτή την περίπτωση ίσως θελήσετε να πραγματοποιήσετε την εργασία απευθείας από τη μέθοδο εκτέλεσης της εργασίας στο νήμα της κλήσης. Αργότερα, θα δούμε ορισμένα παραδείγματα των σημείων όπου η χρήση της συνένωσης αποκτά περισσότερο νόημα.

Το τελευταίο τμήμα κώδικα χρησιμοποιεί το αποτέλεσμα της εργασίας. Το αποτέλεσμα είναι το αντικείμενο IStatus που επιστρέφεται από τη μέθοδο run() της εργασίας. Μπορείτε να χρησιμοποιήσετε αυτό το αποτέλεσμα για να επαναφέρετε τυχόν απαραίτητα αντικείμενα από τη μέθοδο εκτέλεσης της εργασίας. Το αποτέλεσμα μπορεί επίσης να χρησιμοποιηθεί για να δηλώσει αποτυχία (επιστρέφοντας το IStatus με σοβαρότητα IStatus.ERROR), ή ακύρωση (IStatus.CANCEL).

Κοινές λειτουργίες εργασίας

Είδαμε τον τρόπο προγραμματισμού μιας εργασίας και αναμονής της ολοκλήρωσής της, αλλά υπάρχουν πολλά άλλα ενδιαφέροντα πράγματα τα οποία μπορείτε να κάνετε με τις εργασίες. Αν προγραμματίζετε μια εργασία και στη συνέχεια αποφασίσετε ότι δεν τη χρειάζεστε πλέον, μπορείτε να τη διακόψετε με χρήση της μεθόδου cancel(). Αν η εκτέλεση της εργασίας δεν είχε ξεκινήσει κατά την ακύρωση, η εργασία απορρίπτεται αμέσως και δεν εκτελείται. Αν, αντίθετα, η εργασία είχε ήδη ξεκινήσει να εκτελείται, εξαρτάται από την εργασία εάν θα απαντήσει στην ακύρωση. Όταν επιχειρείτε να ακυρώσετε μια εργασία, η αναμονή με χρήση της μεθόδου join() είναι πολύ βολική. Εδώ βλέπετε ένα κοινό ιδίωμα για την ακύρωση μιας εργασίας, και την αναμονή μέχρι την ολοκλήρωση της εργασίας, πριν προχωρήσετε:

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

Αν η ακύρωση δεν ενεργοποιηθεί άμεσα, τότε η μέθοδος cancel() θα επιστρέψει σφάλμα και το στοιχείο υποβολής κλήσης θα χρησιμοποιήσει τη μέθοδο join() προκειμένου να περιμένει την επιτυχή ολοκλήρωση ακύρωσης της εργασίας.

Η μέθοδος sleep() έχει ελαφρώς μειωμένη αποτελεσματικότητα από τη μέθοδο ακύρωσης. Πάλι, αν η εργασία δεν έχει ξεκινήσει την εκτέλεση, αυτή η μέθοδος θα τοποθετήσει την εργασία σε μόνιμη κατάσταση αναμονής. Η εργασία θα παραμείνει γνώριμη για την πλατφόρμα και μια κλήση wakeUp() θα προκαλέσει την προσθήκη της εργασίας στην ουρά αναμονής όπου και τελικά θα εκτελεστεί.

Καταστάσεις εργασίας

Μια εργασία διανύει διάφορες καταστάσεις κατά τη διάρκεια ζωής της. Η διαχείρισή της είναι δυνατή μέσω API όπως τα cancel() και sleep(), αλλά η κατάστασή της αλλάζει καθώς η πλατφόρμα εκτελεί και ολοκληρώνει την εργασία. Οι εργασίες μπορούν να μετακινούνται μεταξύ των ακόλουθων καταστάσεων:

Μια εργασία μπορεί να τοποθετηθεί σε κατάσταση αδράνειας μόνον εάν βρίσκεται σε κατάσταση ΑΝΑΜΟΝΗΣ. Η απενεργοποίηση μιας εργασίας που βρίσκεται σε κατάσταση αδράνειας, την επαναφέρει σε κατάσταση ΑΝΑΜΟΝΗΣ. Η ακύρωση μιας εργασίας την επαναφέρει στην κατάσταση ΚΑΜΙΑ.

Αν η πρόσθετη λειτουργία σας χρειάζεται να γνωρίζει την κατάσταση μιας συγκεκριμένης εργασίας, μπορεί να καταχωρήσει μια λειτουργία ακρόασης αλλαγής εργασίας η οποία ειδοποιείται καθώς η εργασία διανύει τον κύκλο ζωής της. Είναι μια χρήσιμη ενέργεια για την εμφάνιση της προόδου ή την υποβολή αναφοράς σχετικά με κάποια εργασία.

Λειτουργίες ακρόασης αλλαγής εργασίας

Η μέθοδος Job της λειτουργίας addJobChangeListener μπορεί να εφαρμοστεί για την καταγραφή μιας λειτουργίας ακρόασης σε μια συγκεκριμένη εργασία. Η λειτουργία IJobChangeListener καθορίζει το πρωτόκολλο για την απάντηση στις αλλαγές κατάστασης μιας εργασίας:

Σε όλες αυτές τις περιπτώσεις, η λειτουργία ακρόασης παρέχεται με ένα συμβάν IJobChangeEvent που καθορίζει την εργασία η οποία υποβάλλεται σε αλλαγή κατάστασης καθώς και την κατάστασή της μετά την ολοκλήρωσή της (εάν ολοκληρώνεται).

Σημείωση: Οι εργασίες αθορίζουν επίσης τη μέθοδο getState() για την απόκτηση της (σχετικά) τρέχουσας κατάστασης μιας εργασίας. Ωστόσο, αυτό το αποτέλεσμα δεν είναι πάντα αξιόπιστο αφού οι εργασίες εκτελούνται σε διαφορετικό νήμα και μπορεί να αλλάξουν κατάσταση ξανά τη στιγμή της επιστροφής της κλήσης. Οι λειτουργίες ακρόασης αλλαγής εργασίας είναι οι συνιστώμενοι μηχανισμοί για την εντοπισμό των αλλαγών κατάστασης σε μια εργασία.

Η λειτουργία διαχείρισης εργασίας

Η λειτουργία IJobManager καθορίζει το πρωτόκολλο για την εργασία με όλες τις εργασίες στο σύστημα. Οι πρόσθετες λειτουργίες οι οποίες εμφανίζουν την πρόοδο ή διαφορετικά εργάζονται με την υποδομή της εργασίας μπορούν να χρησιμοποιήσουν τη μέθοδο IJobManager προκειμένου να εκτελέσουν εργασίες όπως η αναστολή όλων των εργασιών στο σύστημα, ο εντοπισμός της εργασίας που εκτελείται ή η λήψη σχολίων προόδου σχετικά με μια συγκεκριμένη εργασία. Η λειτουργία διαχείρισης εργασίας της πλατφόρμας μπορεί να αποκτηθεί με χρήση του API της Πλατφόρμας:

      IJobManager jobMan = Platform.getJobManager();

Οι πρόσθετες λειτουργίες που ενδιαφέρονται για την κατάσταση όλων των εργασιών στο σύστημα μπορούν να καταχωρήσουν μια λειτουργία ακρόασης αλλαγής εργασίας στη λειτουργία διαχείρισης εργασίας αντί να καταγράψουν λειτουργίες ακρόασης σε πολλές μεμονωμένες εργασίες.

Οικογένειες εργασιών

Ορισμένες φορές είναι ευκολότερο για μια πρόσθετη λειτουργία να εργαστεί με μια ομάδα σχετικών εργασιών ως μεμονωμένη μονάδα. Αυτό επιτυγχάνεται με χρήση των οικογενειών εργασίας. Μια εργασία δηλώνει ότι ανήκει σε μια ορισμένη οικογένεια αντικαθιστώντας τη μέθοδο belongsTo:

   public static final String MY_FAMILY = "myJobFamily";
   ...
   class FamilyJob extends Job {
      ...
      public boolean belongsTo(Object family) {
         return family == MY_FAMILY;
      }
   }
Το πρωτόκολλο IJobManager μπορεί να χρησιμοποιηθεί για την ακύρωση, συνένωση, αδράνεια ή εύρεση όλων των εργασιών σε μια οικογένεια:
   IJobManager jobMan = Platform.getJobManager();
   jobMan.cancel(MY_FAMILY);
   jobMan.join(MY_FAMILY, null);

Από τη στιγμή που οι οικογένειες εργασίας αντιπροσωπεύονται με χρήση αυθαίρετων αντικειμένων, μπορείτε να αποθηκεύσετε μια κατάσταση στην ίδια την οικογένεια εργασίας, και οι εργασίες μπορούν να δομήσουν με δυναμικό τρόπο αντικείμενα οικογένειας ανάλογα με τις ανάγκες. Είναι σημαντικό να χρησιμοποιείτε αντικείμενα οικογένειας τα οποία είναι σχεδόν μοναδικά, προκειμένου να αποφύγετε τυχαίες αλληλεπιδράσεις με οικογένειες που δημιουργούνται από άλλες πρόσθετες λειτουργίες.

Οι οικογένειες είναι επίσης ένας εύχρηστος τρόπο για τον εντοπισμό ομάδων εργασιών. Μπορείτε να χρησιμοποιήσετε τη μέθοδο IJobManager.find(Object family) προκειμένου να εντοπίσετε, κάποια δεδομένη στιγμή, χρήσεις όλων των καταστάσεων των εργασιών, όπως είναι οι καταστάσεις εκτέλεσης, αναμονής και αδράνειας.