Esittelin kirjoituksen edellisessä osassa kaksi erilaista tapaa toteuttaa tilakone ohjelmointikielen tasolla. Ensimmäistä tapaa kutsuimme tapahtumapohjaiseksi malliksi. Sitten esittelin tästä ”standardimallista” hieman poikkeavan mallin, jota kutsuimme tilaa lukevaksi tai pollaavaksi malliksi. Tässä sarjan viimeissä osassa kuvaan minkälaisia käytännön ongelmia tilakoneen toteutuksessa voi olla, ja miten mallit pystyvät vastaamaan näihin haasteisiin.

Tilaräjähdysongelma

Kun erilaisten tapahtumien ja niitä lähettävien rinnakkaisten toimijoiden lukumäärä kasvaa, tapahtuu tilakoneen tilasiirtymien sekä ulospäin lähtevien käskyjen kaikkien mahdollisten lomitusten määrässä tyypillisesti eksponentiaalinen kasvu. Jopa yllättävän pienellä määrällä tapahtumia tila-avaruus voi kasvaa niin suureksi, että toiminta alkaa olla ihmisjärjelle mahdoton hahmottaa.

Tapahtumapohjainen tilakone joutuu lähtökohtaisesti käsittelemään jokaisen saapuneen tapahtuman välittömästi, tyypillisesti siinä järjestyksessä kuin ne saapuvat, riippumatta siitä missä tilassa parhaillaan ollaan. Kaikkia mahdollisia tapahtumien yhdistelmiä voi tällöin olla vaikea hahmottaa tilaräjähdysongelman vuoksi.

Tilaa lukeva tilakone ei automaattisesti reagoi tilan muutokseen, vaan tarkastelee viimeisintä tilaa. Hyvänä puolena tässä voidaan nähdä se, että on kohtalaisen helppoa ryhmitellä ja priorisoida vallitsevaa ulkoista tilannetta.

Vertauksena tälle ajatusmallille voidaan kuvitella vaikkapa, että kaveriltasi on tullut Facebook-päivitys, mutta samaan aikaan palosireeni soi. Varmaankin jätät tässä tilanteessa päivityksen omaan arvoonsa ja siirryt rakennuksesta pihalle (niinhän…?!). Vasta kun hälytys on käsitelty ja todettu aiheettomaksi, voit katsoa Facebook-päivityksen.

Vastaavasti tilaa lukeva tilakonetta voidaan lähteä suunnittelemaan siitä lähtökohdasta, että viimeisimmän saatavilla olevan ja tärkeimmäksi luokitellun tiedon perusteella tehdään sillä hetkellä tärkein asia. Mahdollisesti vähemmän tärkeää tietoa maailmasta ei siis tarvitse heti käsitellä, mutta tämä tieto ei myöskään lähtökohtaisesti katoa mihinkään, vaan on käytettävissä kun esimerkiksi palataan vähemmän kriittiseen prosessointiin.

Voiko ongelmaksi sitten tulla se, että pollaava tilakone reagoi tilan muutokseen liian hitaasti? Voi tietysti, jos kyseessä olevassa sovelluksessa on tärkeää käsitellä kaikki maailmassa tapahtuvat muutokset välittömästi, tai jos meidän tarvitsee havaita missä järjestyksessä muutokset ovat tapahtuneet. Toisaalta on syytä huomata, että mikään ei sinänsä pakota kutsumaan tilakonetta ainoastaan tietyin aikaintervallein, vaan tietty ulkoinen tapahtuma voidaan myös linkittää herättämään tilakone. Itse tilakoneen logiikka toimii kuitenkin myös tässä tapauksessa samalla tavalla kuin muulloinkin, eli se tekee päätöksiä sillä hetkellä vallitsevan tilan perusteella — mikä on saattanut jo muuttua siitä hetkestä kun heräte tuli.

Syötteiden vanheneminen

Monien ulkopuolelta saapuvien syötteiden osalta tilanne on se, että vain viimeisimmällä tiedolla on merkitystä. Esimerkiksi jos seuraamme jonkin kohteen paikkaa, usein vain viimeisin paikka on tärkeä. Tapahtumapohjainen tilakone joutuu periaatteessa käsittelemään jokaisen saadun viestin, vaikka samaan aikaan olisi ehtinyt tulla jo uudempaakin tietoa samasta asiasta. Pollaava tilakone taas käsittelee luonnostaan vain kyseisellä hetkellä voimassa olevaa tietoa.

Tämä ja edellä kuvatut syyt vaikuttavat siihen että monissa käytännön tilanteissa tapahtumaohjatussa tilakoneessa tapahtuu enemmän tilasiirtymiä ja se siis käy ajon aikana todennäköisesti paljon suuremman osan potentiaalisesta tila-avaruudesta läpi. Koska syötteet “hyppyyttävät” tilakonetta koko ajan, myös potentiaalisen virhetoiminnon tuleminen esiin on todennäköisempää.

Säikeet ja ajallinen järjestys

Keskeinen kysymys tilakoneen suunnittelussa on se, kuka suorittaa tilakoneesta ulospäin lähtevät ohjaustapahtumat, ja mitä voidaan taata niiden ajallisesta järjestyksestä suhteessa tilasiirtymiin ja toisiinsa.

Suoraviivaisin ratkaisu tapahtumapohjaisen tilakoneen tapauksessa on suorittaa ohjauskomennot suoraan kutsuvassa säikeessä, eli samassa säikeessä kuin se tapahtuma, johon tilakone on reagoimassa. Tällä saavutetaan selkeä ajallinen järjestys: ulospäin lähtevät komennot on aina kokonaan suoritettu ennen kuin palataan tilakoneen kutsusta ja ennen kuin mahdolliset uudet kutsut aiheuttavat uusia tilasiirtymiä.

Ongelmaksi tässä saattaa kuitenkin tulla riippuvuus kutsuvasta ohjelman osasta sinne, mihin ohjauskomento päätyy. Jos esimerkiksi ulospäin lähtevän komennon käsittely järjestelmän jossain osassa (vaikkapa talletus tietokantaan) kestää kauan, se blokkaa täksi aikaa myös sen säikeen, josta tilakoneen siirtymän liipaissut tapahtuma tuli. Tämä säie voi esimerkiksi käsitellä järjestelmän ulkopuolelta saapuvia viestejä, jolloin viestien vastaanotto jumiutuu siksi aikaa.

Tämä ratkaisu voi myös tuoda vaaran lukkiutumisesta, jos tilakoneen lukon sisältä tehty kutsu tarvitsee jossain muussa ohjelman osassa lukon, jonka sisältä taas jokin toinen säie yrittää kutsua tilakonetta.

Vielä eräs erikoinen ongelma syntyy, jos tilakoneesta lähtevä kutsu voikin jotakin kautta palautua tilaa muuttavaksi tapahtumaksi tilakoneeseen. Tällainen kutsu pääsee läpi tilakoneen lukosta ja pystyy siis muuttamaan tilakoneen tilaa ja aiheuttamaan uusia ulospäin lähteviä kutsuja, vaikka edelliseen tilaan liittyvät komennot ovat vielä kesken. Päädymme hämmentävään tilanteeseen, jossa jo suoritetaan johonkin seuraavaan tilaan liittyviä Entry-kutsuja, joiden jälkeen palataan suorittamaan vielä johonkin aikaisempaan tilaan liittyneitä Exit-komentoja — ja kaikki tämä voi tietysti tapahtua vielä useammassa tasossa.

Nämä ongelmat ratkaistaan usein niin, että ulospäin lähtevät komennot ajetaan eri säikeessä, jolloin kutsu ei koskaan blokkaa kutsuvaa säiettä. Tällöin paras ratkaisu on yleensä se, että yksi ja sama säie huolehtii kaikkien ulospäin lähtevien komentojen suorittamisesta, jotta voidaan ainakin taata näiden keskinäinen ajallinen järjestys. Muutoin syntyy helposti tilanne, jossa myöhemmän tilasiirtymän aiheuttama komento (vaikkapa tieto järjestelmän kytkeytymisestä pois päältä) voi ehtiä ennen aiempaa komentoa (vaikkapa hälytysviesti).

Tämäkään järjestely ei takaa koneen ulospäin lähtevien tapahtumien ajallista suhdetta koneen tilasiirtymiin. Esimerkiksi hälytysviestiä voitaisiin olla vielä lähettämässä samaan aikaan kun valvonta on ehditty kytkeä jo pois ja päälle ja vielä uusi koodikin syötetty.

Eräs mahdollisuus tapahtumaohjattuun tilakoneeseen liittyvien ongelmien ratkaisemiseksi on sijoittaa syötteet jonoon sen sijaan että ne menisivät suoraan kutsuina tilakoneeseen. Oma säie lukee syötteitä ja siirtää tilakonetta sekä suorittaa ulkoisia toimenpiteitä. Jonon avulla on myös periaatteessa mahdollista priorisoida viestejä eri järjestykseen sekä suodattaa pois samaan asiaan liittyviä vanhempia viestejä.

Tähän ratkaisuun liittyy kuitenkin monia ongelmia. Viestien määrä jonossa voi kasvaa, jos niitä ei käsitellä riittävän nopeasti suhteessa siihen, miten nopeasti uusia saapuu. Tällöin käsiteltävät tapahtumat alkavat edustaa vanhentunutta tietoa ja tilakoneen toiminnot saattavat olla vääriä todelliseen tilanteeseen nähden. Viestijono voi myös jäädä tyhjentämättä tilanteissa, joissa se on tarpeen (esim. resetointi). Yleisesti ottaen käsittelyä odottava viestijono sekä sen mahdollinen muokkaaminen edellä kuvatuilla tavoilla voivat tuottaa vaikeasti hahmottuvaa ja virhealtista tilallisuutta, joka saattaa tehdä tilakoneen toiminnan suunnittelemisen ja ymmärtämisen entistä vaikeammaksi. Pollaavan tilakoneen tapa reagoida vain viimeisimpään nähtävillä olevaan tietoon on ainakin periaatteessa selkeä: lähtökohtaisesti ei ole jonoa joka kätkisi sisäänsä tiloja eikä mitään erityistä muokkaamista tarvita. Käytännössä tilanteeseen vaikuttaa tietysti paljon se millä tavalla tieto ulkomaailmasta saapuu.

Eräs selkeä etu pollaavassa tilakoneessa on toiminnan aloituksen ja lopetuksen selkeys. Tapahtumapohjaisen tilakoneen tapauksessa vaatii hyvin huolellista suunnittelua, että voimme olla varmoja siitä ajankohdasta, jolloin jokin tapahtuma voi ensimmäisen kerran saapua tilakoneelle muuttamaan sen tilaa, ja vastaavasti milloin tämä voi tapahtua viimeisen kerran. Pollaavaan tilakoneen tapauksessa taas tilanne on selkeä: tilakone alkaa toimia silloin kun sen Poll-metodia kutsutaan ensimmäisen kerran, ja se lakkaa toimimasta sen jälkeen kun viimeinen Poll-kutsu on päättynyt.

Syötteiden epätäydellisyys

Hieman vaikeasti hahmotettava mutta käytännössä merkittävä ongelma tilakoneelle ovat erilaiset syötteissä esiintyvät epätäydellisyydet. Näillä on taipumus korostaa edellä kuvattuja ongelmia.

Yksi epätäydellisyyden muoto voi olla joissakin syötteissä esiintyvä “räpsyminen”. Esimerkiksi infrapunasensori saattaisi heilua nopeasti “päälle” ja “pois” -arvojen välissä ennen asettumista vakaasti “päälle”-tilaan. Tähän signaaliin kytketty tapahtumapohjainen tilakone joutuu tällöin nopeassa tahdissa reagoimaan jokaiseen siirtymään, kun taas tilaa lukeva tilakone näkee todennäköisesti vain vakaan lopputilan.

Toinen samantapainen ongelma on toisiaan ajallisesti lähellä olevien viestin järjestys. Kuvitellaan vaikkapa esimerkissämme että ovi voidaan avata myös avaimella, mutta joskus viesti lukon aukeamisesta ehtiikin perille vasta hieman sen jälkeen kun oven avaamisesta aiheutunut liikehavainto. Tämä aiheuttaa tapahtumapohjaisessa tilakoneessa siirtymisen hälytystilaan. Vastaavassa tilanteessa pollaava tilakone havaitsee tyypillisesti lopputilan eli sekä lukon olevan auki että liikehavainnon, joten se näkee tilan hyväksyttävänä.

Tietysti jos tilakone sattuu tutkimaan tilaa juuri sinä lyhyenä hetkenä, kun liikehavainto on jo päällä, mutta lukon aukeamisesta ei ole vielä tietoa, aiheutuisi nytkin hälytys, mutta tämä on yleensä epätodennäköisempi vaihtoehto. Se, onko tällainen todennäköisyyteen enemmän kuin täydellisyyteen perustuva toiminta suotavaa vai ei, riippuu tietysti täysin sovelluksesta. Ratkaisu joka pyrkii minimoimaan ei-toivottujen tapausten määrän voi olla toimiva, mikäli käsillä ei ole turvallisuuskriittinen ongelma, vaan ei-toivottu tapaus aiheuttaa ainoastaan jonkin hyväksyttävissä olevan kustannuksen (esim. vartijan turha käynti). Joissakin tapauksissa signaaleissa esiintyvä epätäydellisyyden määrä voi tehdä täydellisestä järjestelmästä selvästi monimutkaisemman (mikä taas tuo myös uusia virhemahdollisuuksia) ellei jopa käytännössä mahdottoman toteuttaa.

Yhteenveto

Mitään universaalisti paremmin toimivaa hopealuotia ei pollaava tilakone siis tarjoile, vaan sen soveltuvuutta täytyy pohtia kuhunkin tilanteeseen erikseen. Yhteenvetona aiemmasta, pollaavaan tilakoneen käyttöä voi harkita ainakin, jos

  • Tapahtumia ja toimijoita on paljon
  • Useimmat ulkopuolelta saapuvat tapahtumat eivät vaadi aivan välitöntä käsittelyä
  • Monissa syötteissä vain viimeisimmällä tiedolla on merkitystä, eikä meidän tarvitse yleensä pitää kirjaa eri tapahtumien järjestyksestä
  • Syötteissä voi esiintyä epämääräisyyttä kuten “räpsymistä” tai satunnaista järjestyksen vaihtumista

Tilakoneen toteuttamiseen liittyy tietysti monia muitakin kysymyksiä kuin edellä käsitellyt. Käytännössä tilakoneen suunnittelua vaikeuttaa usein esimerkiksi se, että tilakone joutuu toteuttamaan samanaikaisesti montaa toisistaan ainakin osittain riippumatonta toiminnallisuutta. Esimerkissämme tilakone voisi myös vaikkapa seurata lämpötilaa ja säätää ilmastointia. Tietty lämpötila voisi tällöin aiheuttaa hälytyksen, mutta toisaalta tämä toiminnallisuus on erillinen koodilukon seuraamisesta.

Tämän kaltaisessa tapauksessa tilakoneen tilojen ja siirtymien määrä voi kasvaa suureksi sen vuoksi, että tilakone esittää tietyssä mielessä tuloa useasta eri tilakoneesta. Yksi mahdollisuus voi tällöin olla yrittää jakaa tilakone useaksi pienemmäksi alitilakoneeksi, joiden toiminta on sopivasti synkronoitu keskenään. Tämä aihe olisikin sitten jo oman kirjoituksensa arvoinen.