Agenten und Dienste in AMETAS zu programmieren ist eine sehr interessante Aufgabe. Wichtig dabei ist jedoch das Einhalten gewisser Entwurfsprinzipien:
Autonomie der Agenten
Asynchronität der Nachrichten
Es gibt - wie in vielen Fällen - auch hier das Problem, sich
eher vom Konzept oder eher von einer effizienten Programmierung
leiten zu lassen. Beides ist selten möglich: Effiziente
Programmierung ist häufig dadurch geprägt, daß
konzeptionelle Strukturen trickreich umgangen oder
ausgenutzt werden. Der vordergründige Zugewinn wird
später durch Inkompatibilität mit konzeptkonformen
Erweiterungen relativiert.
AMETAS-Agenten sind so gestaltet, daß sie keinen Zugriff auf Objekte außer ihrem eigenen Treiber erhalten. So kann verhindert werden, daß sie Methoden unberechtigterweise auf dem Stellenobjekt oder anderen Objekten aufrufen.
Um mit anderen Agenten oder mit Diensten Daten auszutauschen, werden Nachrichten verwendet. Diese werden durch das Postfachsystem jeder einzelnen Stelle verwaltet.
Wenn ein Agent auf eine Funktionalität zugreifen soll, die ihm an einer Stelle geboten wird, bieten sich daher zwei Mechanismen an:
Der Dienst ist ein AMETAS-Dienst. In diesem Falle ist es nicht möglich, ihn durch Methodenaufrufe anzusprechen. Es ist stattdessen erforderlich, Nachrichten zu formulieren und sie an ihn zu schicken.
Der Dienst ist eine unmittelbare Erweiterung der Stelle, die an allen Stellen eines Agentensystems zur Verfügung steht oder stehen muß. In diesem Fall ist es möglich, den Treiber durch entsprechende Methoden zu erweitern und die zusätzliche Funktionalität innerhalb einer vorhandenen Klasse oder als zusätzliche Klasse zu realisieren.
Natürlich bietet sich die zweite Möglichkeit nur den
Entwicklern der AMETAS-Plattform an und ist mit einer
Versionsänderung verbunden. Es wäre so etwa denkbar, daß
gewisse Funktionen sehr effizient aufgerufen werden können.
Zusätzliche Erweiterungen von anderer Seite ist im Prinzip nur
durch AMETAS-Dienste möglich. Dies erfordert u.a. eine
ausführliche Nachrichtenverarbeitung usw. Damit ist klar, daß
die Effizienz leidet, was nicht zu vermeiden ist.
AMETAS bietet die Möglichkeit, Agenten autonom zu
programmieren. Da keine Schnittstellen auf Sprachebene existieren,
ist eine Bindung an einen Aufrufer nicht möglich. Konzeptionell
sauber wäre es, in regelmäßigen Abständen nach
neu eingetroffenen Nachrichten zu sehen. Sind welche vorhanden, kann
der Agent entscheiden, ob und wann er sie verarbeiten will. Wenn
nicht, wird der Auftrag in einer bestimmten Weise fortgesetzt.
Diese
fortgesetzte Abfrage (auch Polling) beeinträchtigt in
erheblicher Weise die Effizienz der Agentenanwendung. Wenn die Rate
zu hoch gewählt wird, verlangsamt sich die gesamte Abwicklung
dieses Agenten wie auch aller anderen; ist sie zu niedrig, dann
leidet die Reaktivität.
Ein konzeptioneller Ausweg ist der Einsatz von Sensoren. Diese sind in AMETAS durch das Ereignisverarbeitungssystem (Event Handling) zum Teil repräsentiert: Wenn Nachrichten eintreffen, "spürt" der Agent deren Eintreffen durch die Ausführung einer speziellen EventHandler-Methode. Der Haupt-Programmablauf kann dann entsprechend reagieren.
Wie muß das Agentenprogramm überhaupt gestaltet werden, wenn man Events verarbeiten möchte?
Der Agent muß sein Interesse an einem Eventtyp der Stelle mitteilen.
Der Agent muß eine oder mehrere Methoden implementieren, die im Falle eines Ereignisses ausgeführt werden sollen.
Der Programmfluß des Agenten muß durch den eingetroffenen Event beeinflußt werden.
Um einen Programmfluß zu ändern, gibt es in Java - wie in vielen anderen Sprachen auch - den if...then...else-Konstrukt (und ähnliche andere Konstrukte wie while). Aufgrund der Nichtvorhersagbarkeit des Ereigniszeitpunkts - bedingt durch die Asynchronität - ist es erforderlich, die if-Anweisung wiederholt auszuführen, bis ein Ereignis eintritt (oder eben while zu benutzen). Diese wiederholte Abfrage einer Zustandsvariablen ist jedoch wiederum eine Variante des Polling, was wir hier als inneres Polling bezeichnen wollen. Allerdings läuft das innere Polling wesentlich schneller und effizienter ab.
Um dieses innere Polling zu verhindern,
wird häufig folgende Strategie angewendet: Das
PlaceUserDriver-Objekt, auf das der Agent Zugriff hat, erlaubt das
Anhalten des Treiberthreads durch die Methode suspendThr, die
ihrerseits Thread.suspend aufruft. In dieser Weise
findet man dies in der Klasse AMETASPlaceUserDriver in der Methode
requestService_WaitAndUnregister.
|
... |
|
public boolean handleMessageEvent(...) { |
Diese Technik funktioniert , obwohl der Agent nur einen
treibenden Thread besitzt (im Treiber): Der Thread, der für die
Ausführung von handleMessageEvent verantwortlich
zeichnet, läuft im EventManager der Stelle. Er sorgt
dafür, daß die Programmausführung des Agenten
fortgesetzt wird.
Natürlich entbehrt diese
Strategie nicht einer gewissen Eleganz: Der Agent wartet ohne allzu
großem Ressourcenverbrauch, bis eine bestimmte Nachricht
eingetroffen ist. Es gibt weder inneres noch äußeres
Polling. Im Prinzip handelt es sich um eine Synchronisation
der asynchronen Nachrichten. Jedoch ist sie für die Entwicklung
von Agenten nicht zu empfehlen: Der Agent verliert die
Kontrolle über sich, ohne eine Chance zu besitzen, sie
wiederzuerlangen. Es ist - zieht man einen Vergleich zur realen Welt
- als würde man sich eine Narkose verpassen lassen. Man geht
fest davon aus, irgendwann wieder aufzuwachen. Oder? Wer hat sich
schon einmal in Narkose versetzen lassen, um das Warten auf ein
bestimmtes Ereignis zu verkürzen?
Eine geeignete Implementierung könnte
so aussehen:
|
... |
|
if (bGivenUp) {
|
|
else { |
|
public boolean handleMessageEvent(...) { |
Konzept hin, Effizienz her - es geht hier nicht darum,
irgendwelche hehre Prinzipien hochzuhalten, die eh ein Großteil
der Agentenprogrammierer nicht konsequent umsetzen. Man könnte
fragen: Was nützen die besten Konzepte, wenn sie unpraktikabel
sind? Oder: Wer wird es auf sich nehmen, so umständlich zu
programmieren, wenn es auch einfach geht? Und wenn AMETAS es
erzwingt, aber das BestAgentSystemEver es eben erlaubt... Daher
lautet mein wesentlicher Einwand gegen die "einfache"
Lösung: Der Agent hat keine Möglichkeit, zu entscheiden, ob
die erwartete Nachricht jemals eintreffen wird.
Es sollte Teil der Konzeption eines Agenten sein, angesichts asynchroner Nachrichten, deren Verarbeitung insbesondere durch einen anderen Agenten nicht gesichert ist, Maßnahmen zu definieren, die im Falle des Nichteintreffens einer Antwort erfolgen müssen.
Eine ähnliche Vorgehensweise findet man in der Sprache Java
durch das Ausnahmen-Konzept (Exceptions) im Falle von
Fehlerzuständen. Eine mögliche Alternative zur obigen
Lösung kann das "Schlafen" sein (Thread.sleep);
damit ist immerhin die Chance gegeben, daß der Programmablauf
innerhalb des Agenten fortschreitet.
Das AMETAS-Agentensystem ist ein System aus Objekten unterschiedlicher Kategorien; dabei sind stationäre Objekte (Stellen und Dienste, auch Benutzeradapter) und bewegliche Objekte (Agenten) zu unterscheiden. Die Verhaltensbescheibung jedes Objekts ist generell unsichtbar; der Zustand ist privat (bis auf Teile, die allen Objekten eigen sind). Alle diese Objekte können in ihrem Inneren beliebige Verfahren einsetzen, um Informationen zu verarbeiten; zwischen den Objekten werden Informationen über Nachrichten ausgetauscht. Die Zustellung von Nachrichten obliegt der Infrastruktur des Systems. Der Transit einer Nachricht ist für die Objekte transparent; sie verfügen über keine Informationen (insbesondere die Dauer) bezüglich des Transits.
Im Kontext objektorientierter Sprachen spricht man in den Modellierungen im übrigen ebenfall von Nachrichten, die zwischen den Objekten ausgetauscht werden. Realisiert werden diese Nachrichten über Prozeduraufrufe. Im Unterschied dazu manifestiert sich das Informationsfluß-Modell in AMETAS in Form von tatsächlich vorhandenen Strukturen. Rechnet man Prozeduraufrufe (genauer: Methodenaufrufe) mit zu den Nachrichten, dann kommt man nicht umhin, zwei Sorten von Nachrichten zu unterscheiden.
Streitbare Punkte. Im Rahmen der Fortentwicklung der Infrastruktur fällt auf, daß ein Teil der von Agenten nachgefragten Funktionen durch Methodenaufrufe auf dem Treiber, ein anderer Teil durch AMETAS-Nachrichten genutzt werden. Beispiel: Ein Agent löst seine Migration durch Aufruf von AMETASAgentDriver.go(...) aus. Eine Alternative wäre es, die Stelle selbst zum Kommunikationspartner zu machen, die Nachrichten verstehen kann - zumal sie Nachrichten an Agenten senden kann.
Ein anderes Beispiel ist der PlaceNameService: Derzeit wird er "nebenher" von AMETAS-Stellen erledigt, ohne in das Agentensystem integriert zu sein. Zwar ist so die Implementierung einfacher; aber angenommen, man schreibt einen Konfigurationsagenten, der Daten vom PlaceNameService benötigt. Möchte man gänzlich auf die Erstellung eines Dienstes verzichten, müßte man den PlaceUserDriver erweitern. Allerdings benötigen externe Schnittstellen wie AMSI und AMAI einen PlaceNameService, ohne in das Agentensystem eingebunden zu sein.
Es ist klar, daß nicht alle Methodenaufrufe durch
Nachrichten ersetzt werden können. Es muß letztendlich
eine Methode zum Nachrichtensenden und -empfangen geben. Ein
Kompromiß wäre es, einen "Wrapper"-Dienst zu
entwerfen, der einige dieser Stellenfunktionen (auch die go-Methode!)
als Dienst den Agenten zur Verfügung stellt. Den Entwicklern
bleibt freigestellt, diesen Dienst zu verwenden oder auf die
bestehenden Methoden zurückzugreifen.
Zugegebenermaßen erscheint es häufig unumgänglich, einen Agenten auf eine Antwort warten zu lassen, bis diese endlich eintrifft. Genauso kann die ständige Verwendung asynchroner Nachrichten sehr aufwendig erscheinen, und man würde sich in einigen Fällen wünschen, doch irgendwie einen Methodenaufruf zur Verfügung zu haben. Jedoch handelt es sich bei Agenten nicht nur um ein neues Konstrukt in der objektorientierten Welt, sondern um ein neues Programmierparadigma. Das heißt, daß zu erwarten ist, daß die Umstellung konventioneller Anwendungen durchaus ein hohes Maß an Aufwand - wenn nicht sogar einen völligen Neuentwurf - erfordern kann. Ähnliches galt bereits für den Wechsel zur objektorientierten Programmierung: Ein C++-Compiler übersetzt eben auch C-Programme.
Fazit: Es obliegt jedem Entwickler, seine Agenten mehr oder weniger zur AMETAS-Sichtweise konform zu programmieren und die gegebenen Freiräume zu nutzen. Es kann noch lange nicht davon gesprochen werden, daß das Paradigma in allen Einzelheiten festgelegt ist. Warnen muß man jedoch davor, bestehende Konstrukte nicht-agentenbasierter Anwendungen in das Agentensystem zu pressen. Das Resultat kann nur heißen: Es läuft viel langsamer als vorher, dafür ist es viel aufwendiger.
Nach meinem Ermessen spielen Asynchronität und Autonomie die zentrale Rolle beim Entwurf von Agentenanwendungen - in ähnlicher Weise, wie Ableitung, Methoden usw. für C++ oder andere OO-Sprachen zentral sind.
mz, 03.11.98