Synkronisering

Med asynkron programmering dukker ofte behovet opp for kontrollere hvilken tråd som bruker bestemte ressurser på et gitt tidspunkt.

Det er ganske vanlig å kun tillate èn tråd, i stedet for flere likt. Har man ingen kontroll risikerer man at oppdaterte/endrede data blir mangelfulle, at ting ikke blir gjort i riktig rekkefølge, o.s.v. Her kommer lock, Monitor, [Synchronization] med flere, inn i bildet.

Lock og Monitor

Vanligvis kommer man langt med lock som i praksis fungerer slik:

lock (this) // hvis dette er inni en privat metode
{
   ...
}

.. eller slik:

private object låsObjekt = new object(); // utenfor metoden
...
lock (låsObjekt) // hvis dette er inni en åpen metode
{
   ...
}

Og siden lock bare er en kortform for Monitor kan man visst i følge boka også kjøre på med følgende:

private object låsObjekt = new object(); // utenfor metoden
...
Monitor.Enter(låsObjekt) // inni åpen eller stengt metode
try
{
   ...
}
finally
{
   Monitor.Exit(låsObjekt)
}

Fordelen er visst at dette muliggjør større kontroll fordi Monitor blant annet har støtte for å kontakte ventende tråder.

[Synchronization]

Vil man ordne låsing for en hel klasse trenger man bare å plassere [Synchronization] fremfor klassedefinisjonen sin. Ulempen er at dette vil gjelde for absolutt alt innhold – til og med de metodene som ikke trenger det.

Asynkron programmering

Etter å ha brukt ca èn uke har jeg nå fått god oversikt over de forskjellige måtene man kan bedrive asynkron programmering på i NET. Og ikke bare har jeg fått repetert gammelt stoff, jeg har også fått lært mye nytt.

Hvorfor jeg ikke har brukt tid på dette før vet jeg ikke helt. Jeg har alltid hatt for vane å opprette egne tråder og ikke tenkt mer på det, det er jo så enkelt og intuitivt:

new Thread(() =>
{
   ...
}).Start();

Delegater

Først, for å i det hele tatt kunne komme i gang med å lære hvilke alternativer som fantes måtte jeg lære hvordan delegater fungerer: En delegat er simpelthen bare en egen type for å holde på referanser til funksjoner/metoder. Disse brukes fordi man ikke alltid kan benytte de aktuelle funksjonene/metodene direkte. En delegat har samme signatur som de funksjonene/metodene den skal kunne referere til.

Om vi først tar utgangspunkt i to enkle mattefunksjoner:

static double Pluss(double tall1, double tall2)
{
   return tall1 + tall2;
}
static double Minus(double tall1, double tall2)
{
   return tall1 - tall2;
}

.. så gir dette utgangspunktet for hvordan definisjonen til delegaten må se ut:

delegate double MatteFunksjon(double tall1, double tall2);

Deretter kan vi opprette en stk. for hver av de opprinnelige funksjonene:

MatteFunksjon PlussDelegat = new MatteFunksjon(Pluss);
MatteFunksjon MinusDelegat = Minus; // en kortere form

Disse delegatene, altså PlussDelegat og MinusDelegat, kan nå brukes hver gang man ønsker å benytte Pluss og eller Minus i en eller annen asynkron sammenheng.

IAsyncResult

Boka jeg bruker begynte dette temaet med å ta i bruk delegater og (I)AsyncResult.

Et eksempel på hvordan dette fungerer i praksis, med Pluss og PlussDelegat:

IAsyncResult plussAsyncResultat = PlussDelegat.BeginInvoke(2, 2, null, null);
while (!plussAsyncResultat.IsCompleted)
{
   ...
}
double svar = PlussDelegat.EndInvoke(plussAsyncResultat);

Her sendes tallene 2 og 2 til Pluss (som skal kjøre asynkront) via PlussDelegat, og det returneres en instans av AsyncResult via en IAsyncResult-referanse. Denne brukes helt til slutt for å hente ut svaret.

I mellomtiden venter man på at plussAsyncResultat skal bli ferdig.


En alternativ løsning kan være å benytte en egen funksjon/metode som tar seg av det som skal gjøres etter Pluss er ferdig.

I praksis kan man sause alt sammen inn i BeginInvokePlussDelegat:

IAsyncResult plussAsyncResultat = PlussDelegat.BeginInvoke(2, 2, new AsyncCallback(PlussResultat), new double[] {2,2});

.. hvor PlussResultat får ansvaret for å avslutte:

void PlussResultat(IAsyncResult iar)
{
   double[] tall = (double[]) iar.AsyncState;
   double svar = PlussDelegat.EndInvoke(iar);
   ...
}

Dermed blir det aldri noe venting i den opprinnelige tråden.

Thread

Min favoritt som er egne tråder er nok fortsatt det alternativet jeg liker best:

Thread tråd = new Thread(() =>
{
   double svar = Pluss(2, 2);
   ...
});
tråd.Start();

I stedet for anonyme funksjoner kan man selvsagt også bruke en funksjon/metode som allerede eksisterer, hvis den har riktig signatur:

Thread tråd = new Thread(FunksjonMedRiktigSignatur); // gitt funksjon må returnere void uten å ta inn parametere

.. eller:

Thread tråd = new Thread(FunksjonMedRiktigSignatur); // gitt funksjon må returnere void og akseptere èn stk. object inn
...
tråd.Start(new double[] {2, 2});

Det som kan være et problem her er at ikke noe returneres. En mulig løsning er da å lagre resultatet i en variabel opprettet utenfor tråden, men siden dette åpner for å få en «race condition» må man være påpasselig med hvordan man gjør det.

ThreadPool

Mye omtalte ThreadPool som ofte er anbefalt, men som jeg aldri har lært å like:

WaitCallback plussDelegatForTrådSamling = new WaitCallback((data) =>
{
   double svar = Pluss(2, 2);
   ...
});
ThreadPool.QueueUserWorkItem(plussDelegatForTrådSamling);

Også her kan man bruke en eksisterende funksjon/metode hvis noen passer:

WaitCallback minusDelegatForTrådSamling = new WaitCallback(FunksjonMedRiktigSignatur); // gitt funksjon må returnere void og ta inn èn stk. object
ThreadPool.QueueUserWorkItem(minusDelegatForTrådSamling, new double[] {2, 2});

Task

Et alternativ jeg har begynt å like er Task siden det gir lite kode:

Task.Factory.StartNew(() =>
{
   double svar = Pluss(2, 2);
   ...
});

Også her kan vi angi en eksisterende funksjon/metode hvis signaturen passer:

using (Task plussOppgave = Task.Factory.StartNew(FunksjonMedRiktigSignatur)) // gitt funksjon må returnere void uten å ta inn parametere
{
   while (!plussOppgave.IsCompleted)
   {
      ...
   }
}

Task er støttet fra og med versjon 4.0 av NET.

async og await

I motsetning til (I)AsyncResult, Thread, ThreadPool og Task så er async og await litt vanskelig å komme inn i til å begynne med.

Først et eksempel på hvordan funksjonen/metoden kan se ut her for at man skal kunne bruke den asynkront:

async void AsyncPlussResultat(double tall1, double tall2)
{
   await Task.Run(() =>
   {
      double svar = Pluss(tall1, tall2);
      ...
   });
}

.. som kan brukes med:

AsyncPlussResultat(2, 2);

(Man trenger riktignok ikke alltid mate inn data slik som her, man kan ha funksjoner/metoder som ikke tar parametere i det hele tatt.)

Som med Thread og ThreadPool er det også her litt kronglete å få fraktet data helt tilbake dit det opprinnelige kallet begynner. Men ved å bruke async og await med Task er det fullt mulig:

async Task<double> AsyncPluss(double tall1, double tall2)
{
   return await Task.Run(() =>
   {
      double svar = Pluss(tall1, tall2);
      return svar;
   });
}

.. hvor denne kalles ved å f.eks. bruke følgende:

Task<double> oppgave = AsyncPluss(2, 2);
while (!oppgave.IsCompleted)
{
   ...
}
double svar = oppgave.Result;

Altså får man også her returnert et svar, om man er villig til å vente litt.

Parallel

Siste alternativet (akkurat nå) er Parallel, her med bruk av Invoke:

Parallel.Invoke(() =>
{
   double svar = Pluss(2, 2);
   ...
},
() =>
{
   double svar = Minus(2, 2);
   ...
});

Parallel har også For og ForEach for å trygt kunne kjøre gjennom «collections», og er støttet fra og med 4.6 av NET.

TBC

Mal for klasse med egen ressursfrigjøring

Har man en klasse som benytter mye «managed» minne kan man selv implementere og sende kall til Dispose() for å frigjøre minnet med en gang man er ferdig.

Om man bruker «unmanaged» minne bør også dette frigjøres her, i tillegg må man implementere metoden ~NavnPåKlasse() som GC kan bruke i tillfelle man glemmer å sende kall til Dispose() på egenhånd.

Her er en ferdig mal som inneholder alt dette:

class Klasse : IDisposable
{
   private bool brukerFortsattRessurser = true;

   ~Klasse()
   {
      // Frigjør bare 'unmanaged' ressurser:
      FrigjørRessurser(false);

      Console.WriteLine("~Klasse() kalt");
   }

   public void Dispose()
   {
      // Frigjør både 'managed' og 'unmanaged' ressurser:
      FrigjørRessurser(true);

      // Fortell GC at ~Klasse() ikke trengs å kjøres:
      GC.SuppressFinalize(this);

      Console.WriteLine("Dispose() kalt");
   }

   private void FrigjørRessurser(bool beggeTyper)
   {
      // Frigjør både 'managed' og 'unmanaged' ressurser hvis dette ikke er gjort:
      if (brukerFortsattRessurser)
      {
         // Frigjør 'managed' ressurser her:
         if (beggeTyper)
         {
            // ..
         }

         // Frigjør 'unmanaged' ressurser her
         // ..

         brukerFortsattRessurser = false;
      }

      Console.WriteLine("FrigjørRessurser(" + beggeTyper + ") kalt");
   }
}

Her bør hver nye metode man lager først kontrollere at brukerFortsattRessurser er sann.

Er Dispose() allerede kjørt burde nye metodekall gi unntak (altså «exception»).

using

Noen gang lurt på hva som er greia med using (Klasse objekt = new Klasse())?

Vel, dette er enkelt og greit en kortform for try med finally hvor man i sistnevnte sender et kall til Dispose() for å gjøre seg ferdig med brukte ressurser i det dynamiske minnet (altså på «heap»-en).


using (RessursKrevendeKlasse k = new RessursKrevendeKlasse())
{
   // .. bruk av k.metode(..) her
}

.. kan derfor sidestilles med:

RessursKrevendeKlasse k = new RessursKrevendeKlasse();
try
{
   // .. bruk av k.metode(..) her
}
finally
{
   k.Dispose();
}

LINQ

Etter å ha brukt noen timer på LINQ de siste dagene viser det seg at dette er utrolig enkelt å komme i gang med – tross alle kilder som sier det motsatte.

Antagelig er det fordi jeg tidligere har jobbet mye med SQL og databaser.


Et svært konkret og enkelt eksempel:

string[] tekst = { "Dette", "er", "en", "test!" };
var ordFraTekstMedBokstavenT = from ord in tekst where ord.Contains("t") select ord;

Her henter man ut alle ord som inneholder bokstaven t.

Disse kan man enkelt ta i bruk for videre bruk:

foreach (var ord in tekst) Console.Write(ord + " "); 

Som dermed skriver ut Dette test!.


Man kan selvsagt behandle andre ting enn bare tekst også:

Bil[] mineBiler = new Bil[]
{
   new Bil("Toyota", "Corolla", 1993),
   new Bil("Toyota", "Corolla Verso", 2003),
};
var bilNyereEnn2000 = from bil in mineBiler where bil.Årgang > 2000 select bil;
Console.Write("Biler fra år 2000 eller senere: ");
foreach (var bil in bilNyereEnn2000) Console.Write(bil);

Her blir resultatet Biler fra år 2000 eller senere: Toyota Corolla Verso 2003.

(Her har klassen Bil en «overloaded» ToString() som forenkler utskrifter.)


TBC