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

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.