Task-Based Asynchronous Pattern (TAP) er et designmønster, der har til formål at forenkle arbejdet med asynkrone operationer i programmering, specielt inden for .NET. En af de primære fordele ved TAP er muligheden for at opnå ikke-blokerende kode, hvilket gør det muligt for applikationer at håndtere flere operationer samtidigt uden at låse brugergrænsefladen eller nedbringe systemets ydeevne. Dette er især vigtigt i applikationer, der kræver realtidsinteraktion, som f.eks. i brugergrænseflader.
I mange tilfælde vil vi støde på metoder, der følger TAP-mønstret, og som vi forventer vil returnere hurtigt. For eksempel i en UI-applikation, hvor billedbehandling udføres asynkront. Men i en webapplikation er det sjældent nødvendigt at bruge metoder som Task.Run til at køre billedbehandling asynkront, medmindre det er nødvendigt for at forhindre at serveren bliver overbelastet med synkrone opgaver, der kunne blokere ressourcerne.
Når man arbejder med asynkrone metoder, kan en praktisk tilgang være at benytte sig af forskellige hjælpefunktioner, der gør det lettere at arbejde med Task-objekter, som er grundlaget for TAP. Disse funktioner gør det muligt at kombinere og kontrollere asynkrone opgaver effektivt. Nogle af de mest nyttige værktøjer inkluderer metoder, der opfører sig som asynkrone metoder, men tilbyder særlige funktioner eller adfærd, såsom metoder til at annullere eller vise fremdrift under en asynkron operation.
Et af de grundlæggende eksempler på asynkrone operationer i .NET er implementeringen af en simpel forsinkelse. Dette svarer til den synkrone metode Thread.Sleep, men uden at blokere en tråd. I stedet for at bruge Thread.Sleep, som kræver, at en tråd bruges til at blokere systemet i et givent tidsrum, kan man bruge en Timer-klasse fra System.Threading. Denne klasse giver mulighed for at planlægge en callback, der kan udføres asynkront efter en bestemt forsinkelse.
En effektiv implementering af en forsinkelse kan se sådan ud:
I denne metode opretter vi en TaskCompletionSource, som bruges til at skabe en Task, der fuldføres, når Timer-objektet afslutter sin opgave. Denne metode er mere effektiv end at bruge Thread.Sleep, fordi den ikke blokerer nogen tråde og giver systemet mulighed for at udføre andre opgaver i mellemtiden.
Ud over forsinkelse findes der flere andre asynkrone værktøjer og metoder, der kan være nyttige i applikationer, der kræver både asynkronitet og kontrol. Eksempelvis findes der "kombinatorer", som er metoder, der bearbejder eksisterende Task-objekter og genererer nye opgaver, som derefter kan behandles. Dette gør det muligt at skabe mere kompleks asynkron funktionalitet ved at kæde opgaver sammen.
Desuden er det vigtigt at forstå, at når man arbejder med TAP, er der flere måder at håndtere fejl på, især når man arbejder med flere asynkrone opgaver samtidigt. Brug af Task.WhenAny eller Task.WhenAll kan hjælpe med at styre flere opgaver samtidigt og sikre, at fejl håndteres korrekt, mens systemets effektivitet bevares.
En anden vigtig aspekt ved asynkrone operationer er muligheden for at annullere en opgave, hvis den ikke længere er nødvendig, eller hvis den tager for lang tid. Ved hjælp af CancellationToken kan man nemt annullere opgaver, der kører asynkront, hvilket er essentiel for at sikre, at applikationen forbliver responsiv.
Når man arbejder med asynkrone metoder, er det også vigtigt at overveje, hvordan man kan vise fremdrift under langvarige operationer. Dette kan gøres ved hjælp af Progress<T>-klassen, som giver mulighed for at opdatere UI'et med information om, hvor langt en asynkron operation er kommet. Dette kan være særligt nyttigt i applikationer, hvor brugeren skal være opmærksom på, hvordan en opgave skrider frem, som f.eks. ved upload af filer eller langsomme beregninger.
En yderligere overvejelse er ressourcehåndtering i asynkrone applikationer. Mens asynkrone metoder ikke nødvendigvis blokerer tråde, kan de stadig forbruge ressourcer, især når mange opgaver kører samtidigt. Det er derfor nødvendigt at implementere mekanismer, der sikrer, at systemet ikke bliver overbelastet af et for stort antal samtidige asynkrone operationer. For eksempel kan en begrænsning af samtidige opgaver ved hjælp af SemaphoreSlim eller en køstyringsmekanisme være nødvendigt for at opretholde ydeevnen.
Når du arbejder med TAP, er det afgørende at have en god forståelse for, hvordan asynkrone operationer fungerer, og hvordan man kan udnytte de værktøjer, der er til rådighed i .NET. Effektiv brug af asynkrone metoder kan ikke kun forbedre applikationens ydeevne, men også gøre koden mere læsbar og vedligeholdelsesvenlig, når den bruges korrekt.
Hvordan async metoder fungerer i C#
Når du arbejder med asynkrone metoder i C#, er der flere vigtige aspekter at forstå, især når det gælder, hvordan metoder håndteres i forbindelse med await og async. Begge disse koncepter er essentielle for at skabe effektiv og ikke-blokerende kode.
For at forstå, hvordan await fungerer, er det nødvendigt at vide, at C# behandler asynkrone metoder forskelligt fra synkrone metoder. En asynkron metode, der bruger async og await, giver mulighed for, at koden kan "pause" og vente på et resultat, uden at blokere tråden, der kører programmet. Dette adskiller sig fra traditionelle synkrone metoder, hvor koden køres sekventielt, og enhver ventetid blokkerer hele tråden.
Når du erklærer en metode som async, ændres dens opførsel. En metode, der returnerer en Task, vil kunne bruges sammen med await, og resultatet bliver behandlet, når opgaven er afsluttet. Hvis metoden returnerer en Task<T>, hvor T er en type, kan den vente på, at den asynkrone opgave bliver afsluttet, og derefter returnere en værdi af typen T.
Der er dog en vigtig pointe at bemærke: selvom en metode er mærket som async, betyder det ikke nødvendigvis, at den indeholder await i sin krop. Det er stadig muligt at implementere asynkrone metoder uden at anvende await, men dette giver ikke de samme fordele ved ikke-blokerende adfærd.
Når du skriver en asynkron metode, skal du være opmærksom på returneringens adfærd. I en traditionel synkron metode skal return anvendes med den korrekte værdi afhængigt af metodens returtype. I en asynkron metode ændres denne adfærd: metoder, der returnerer Task eller Task<T>, har også en return-erklæring, der afslutter metoden. I en metode, der returnerer Task, er returneringen af et resultat ikke nødvendigt; metoden kan afsluttes med blot en return uden værdi.
Det er vigtigt at forstå, at asynkrone metoder er "kontagiøse". Det betyder, at når en metode kalder en asynkron metode, bliver den også nødt til at være asynkron. Hvis du skriver en helper-metode, der henter størrelsen på en webside asynkront, vil den metode, der kalder denne, også skulle være asynkron for at kunne vente på resultatet. Dette skaber en kæde af asynkrone metoder, som potentielt kan sprede sig gennem hele applikationen. Denne kædede struktur kan føles overvældende, men da asynkrone metoder er lette at skrive, bliver det ikke nødvendigvis et problem.
En interessant detalje er, at asynkrone metoder også kan anvende anonyme funktioner, herunder lambdas og delegerede funktioner. Dette giver mulighed for at skrive mere kompakt kode og samtidig bevare de samme asynkrone egenskaber. For eksempel kan en asynkron anonym funktion defineres som en delegeret funktion, der returnerer en værdi. Tilsvarende kan en asynkron lambda anvendes til at returnere en værdi asynkront, hvilket gør det muligt at implementere asynkrone funktioner på flere måder uden at skulle oprette separate metoder.
Når vi dykker ned i, hvad der rent faktisk sker, når await bruges, skal vi forstå, at når programmet når et await-udtryk, bliver den aktuelle tråd frigivet, og metoden sættes på pause. Dette betyder, at metoden "hibernate" (hibernere) i en kort periode, indtil den nødvendige opgave er afsluttet. Dette kræver, at systemet gemmer den aktuelle tilstand af metoden, herunder lokale variabler, parametre, og hvilket punkt i metoden vi er nået til. Når den asynkrone opgave er færdig, vil metoden fortsætte, som om den aldrig var blevet sat på pause.
Denne "hibernation" af metoden betyder, at ressourcerne, der bruges af den, er minimale, da tråden, der kørte metoden, er blevet frigivet. Kun lidt hukommelse bruges til at gemme den nødvendige tilstand, så når opgaven er afsluttet, kan metoden fortsætte uden at forårsage en stor belastning for systemet. Dette gør, at programmet kan køre effektivt og samtidig vente på resultater fra asynkrone opgaver.
Når en metode når et await, vil C# automatisk gemme den aktuelle tilstand af metoden, som om den blev "sat på pause". Denne tilstand inkluderer værdierne af alle lokale variabler, parametre, og eventuelt også hvilken medlemsvariabel der er i brug, hvis metoden er en instansmetode. Denne tilstand gemmes på heap'en og gør det muligt for metoden at genoptage, når den asynkrone opgave er afsluttet.
C# håndterer også konteksten af metoden, når den vender tilbage fra et await. Det kan være nødvendigt at bevare konteksten af tråden, som metoden blev kaldt på, for at sikre, at den fortsætter på den rigtige tråd efter afslutningen af opgaven. Dette kan omfatte trådspecifikke indstillinger, som f.eks. synkronisering af UI-tråde i Windows-applikationer.
For at optimere præstationen kan det dog være nødvendigt at være opmærksom på, hvordan await håndteres. I visse tilfælde kan det være nyttigt at bruge ConfigureAwait(false) for at undgå, at C# forsøger at genoptage metoden på samme kontekst, hvilket kan forbedre ydeevnen i visse applikationer, især i server-side applikationer, hvor det ikke er nødvendigt at vente på en specifik tråd.

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский