I asynkrone metoder er der en væsentlig forskel i, hvordan undtagelser håndteres, sammenlignet med synkrone metoder. En vigtig egenskab ved asynkrone metoder er, at undtagelser ikke kastes umiddelbart ved kaldet af metoden, men først ved await. Dette kan skabe problemer, især hvis man glemmer at await en asynkron metode. Hvis en asynkron metode kaldes uden at blive ventet på, så fanges eventuelle undtagelser ikke, og de går derfor ubemærket hen. Det svarer til at bruge en tom catch-blok, der fanger undtagelser, men ikke gør noget ved dem. Denne praksis er skadelig, da den kan føre til en ugyldig programtilstand og subtile fejl, der først bliver opdaget langt senere i koden.
Det er derfor vigtigt altid at vente på asynkrone metoder for at sikre, at undtagelser bliver håndteret korrekt. En anden ting, der er værd at bemærke, er, at adfærden med at ignorere undtagelser i asynkrone metoder er en ændring, der blev indført med .NET 4.5. Tidligere kunne undtagelser fra Task Parallel Library (TPL) blive genkastet på finalizer-tråden, men dette sker ikke længere. Denne ændring betyder, at udviklere skal være mere opmærksomme på håndteringen af undtagelser i asynkrone metoder.
Undtagelser i Async-void metoder
Asynkrone metoder, der returnerer void, kan ikke awaites, og deres adfærd i forhold til undtagelser er derfor anderledes. Det er ikke altid ønskeligt, at undtagelser fra disse metoder forbliver ubemærkede. Hvis en undtagelse opstår i en async-void metode, bliver den genkastet i den kaldende tråd. Hvis der var en SynchronizationContext ved kaldet af metoden, vil undtagelsen blive sendt til denne. Hvis ikke, bliver den kastet på en trådpooltråd. Dette kan medføre, at applikationen afsluttes, medmindre en undtagelseshåndterer er knyttet til den passende hændelse.
Derfor bør man kun bruge async void metoder i specifikke tilfælde, som for eksempel når metoden kun kaldes eksternt, eller når man er sikker på, at den ikke vil kaste undtagelser.
Fire and Forget
I sjældne tilfælde er det muligt, at man virkelig ikke er interesseret i, om en metode lykkes eller ej, og hvor det at vente på den ville være for kompliceret. I sådanne situationer er det stadig bedst at returnere en Task, men at sende denne Task videre til en metode, der håndterer eventuelle undtagelser. Et eksempel på en sådan metode kunne være en extension-metode, der fortsætter med at håndtere undtagelser og logge dem til et system:
Denne metode håndterer undtagelser ved at logge dem, og sikrer dermed, at der ikke opstår tyste fejl.
AggregateException og WhenAll
En stor forskel i asynkrone operationer er muligheden for, at flere undtagelser kan opstå samtidig. Dette kan ske, når man bruger Task.WhenAll til at vente på et sæt asynkrone opgaver. I et sådant tilfælde kan flere undtagelser kastes på én gang, og det kan være svært at afgøre, hvilken der er den vigtigste. Task.WhenAll skaber en AggregateException, der indeholder en samling af undtagelser. Denne adfærd er integreret i Task, så når en undtagelse opstår i en asynkron metode, bliver den indkapslet i en AggregateException, som derefter placeres i den oprindelige Task.
I de fleste tilfælde vil AggregateException kun indeholde én undtagelse, men når man bruger WhenAll, kan den indeholde flere. Hvis en undtagelse opstår før den første await, kunne den være blevet kastet synkront, men for at sikre en ensartet adfærd kastes undtagelsen i stedet ved den første await. Når undtagelsen fanges, kan man tilgå AggregateException for at få hele listen over undtagelser.
Synkrone undtagelser i asynkrone metoder
Det er muligt at kaste undtagelser synkront i en asynkron metode, men kun hvis undtagelsen angiver en fejl i metodens kald, ikke en fejl, der opstår under kørslen. Hvis man ønsker at kaste en undtagelse synkront, kan man bruge en synkron metode, der tjekker for fejlsituationer, inden den kalder den asynkrone metode. Dette gør det lettere at forstå staksporet ved fejlhåndtering.
For eksempel kan en metode, der henter et favicon, kaste en ArgumentNullException, hvis domænet er null, før den kalder den asynkrone metode:
Denne metode giver lidt mere klare stakspor, men det er ofte en lille gevinst i forhold til den ekstra kompleksitet, det medfører.
Endelig i asynkrone metoder
Det er muligt at bruge try..finally i asynkrone metoder, og det fungerer som forventet: blokken inden finally kører, før metoden afsluttes, uanset om afslutningen sker normalt eller via en undtagelse. Men der er en vigtig forbehold: I asynkrone metoder er der ingen garanti for, at eksekveringen rent faktisk forlod metoden, før den ramte finally blokken. Hvis en metode når et await, kan den blive glemt og garbage collectet, hvilket betyder, at finally-blokken aldrig vil blive udført.
Dette er en vigtig nuance at forstå, da det kan føre til situationer, hvor man fejlagtigt tror, at en oprydning altid vil blive udført, selvom den ikke nødvendigvis sker, hvis metoden ikke afsluttes korrekt.
Hvorfor skal programmer være asynkrone?
Asynkrone programmeringsteknikker er blevet et centralt redskab i moderne softwareudvikling, især når det kommer til applikationer, der skal håndtere lange processer uden at påvirke brugeroplevelsen. I et synkront program vil eksekveringen af kode foregå én linje ad gangen. Dette fungerer fint, når operationerne er hurtige og ikke forstyrrer den overordnede brugeroplevelse. Men når en langvarig operation som en netværksanmodning eller databaseforespørgsel skal køres, risikerer man, at applikationen fryser, fordi den er nødt til at vente på, at operationen afsluttes.
I en synkron verden ville man ofte skrive en ny linje kode, der kører umiddelbart efter den lange operation, hvilket er ganske nemt i blokkerende kode. I den asynkrone verden fungerer dette dog ikke, da den næste linje næsten helt sikkert vil blive udført, før den asynkrone operation er afsluttet. For at løse dette problem er der udviklet et væld af mønstre og teknikker, der hjælper med at køre kode, når den baggrundsoperation, man har igangsat, er færdig. Nogle af de mest anvendte teknikker inkluderer at indsætte koden i den baggrundsoperation, tilmelde sig en hændelse, der aktiveres ved afslutning, eller benytte en delegate eller lambda-funktion (en callback), der bliver kaldt efter afslutning.
Men hvis denne næste operation skal køre på en specifik tråd (for eksempel en UI-tråd i WinForms eller WPF), skal man også tage højde for at stille operationen i kø på den tråd. Det kan hurtigt blive en rodet proces.
Fordelene ved asynkron kode er klare. Den frigør den tråd, den blev startet på, hvilket giver mange fordele. For det første kræver tråde ressourcer, og det er altid godt at bruge færre ressourcer. Der er ofte kun én tråd, der kan udføre en bestemt opgave, som f.eks. UI-tråden, og hvis denne ikke hurtigt bliver frigivet, vil applikationen blive langsom og utilgængelig for brugeren. Desuden gør asynkron programmering det muligt at udnytte parallel computing, hvilket giver programmer mulighed for at blive struktureret på nye, mere effektive måder, hvilket vi vil uddybe i et senere kapitel.
Med introduktionen af de nye nøgleord async og await i C# 5.0, blev det muligt at skrive asynkrone programmer uden at skulle bekymre sig om de komplekse mønstre, som tidligere var nødvendige. Dette er en stor forbedring, der gør det lettere at arbejde med asynkrone operationer, og i de fleste tilfælde kan programmer skrives med et asynkront design uden at det bliver kompliceret eller svært at vedligeholde.
Når vi taler om async, refererer vi til en stil, der gør det muligt at skrive kode, der kører i baggrunden, uden at blokkere den primære tråd. Et eksempel på dette er metoden DownloadStringAsync, der i stedet for at blokere tråden, der kalder den, fortsætter sin eksekvering og afsluttes senere, når downloadet er færdigt. Dette adskiller sig fra den synkrone version, hvor tråden først afslutter downloadet, før den kan fortsætte.
En asynkron metode omdannes af compileren på en måde, der gør koden mere læselig, samtidig med at den opretholder de asynkrone fordele. Kernen i asynkrone metoder er await-nøgleordet, der angiver, at operationen skal køres asynkront. Når metoden når et await-punkt, opdeles den bagvedliggende kode i to sektioner: den første sektion kører synkront, og den anden sektion kører, når den asynkrone operation er afsluttet. Dette betyder, at tråden hurtigt kan frigives, og det tillader andre operationer at blive udført uden at blokere for brugerinteraktioner.
Selvom async og await gør asynkron programmering lettere, løser det ikke alle problemer. Det er vigtigt at forstå de bagvedliggende mekanismer, især når det kommer til fejlbehandling, returnering af værdier, trådhåndtering og performanceoptimering. Selvom koden virker simpel, kan fejl opstå på måder, der er svære at debugge, hvis man ikke forstår, hvad der sker under overfladen. For eksempel skal undtagelser håndteres omhyggeligt i asynkrone metoder, da de opfører sig anderledes end i synkrone metoder.
Desuden, mens asynkron kode kan være meget effektiv, betyder det ikke, at den altid vil være den bedste løsning. I tilfælde af meget enkle opgaver, hvor trådene ikke er belastet, kan den synkrone tilgang være både enklere og hurtigere.
Det er vigtigt at forstå, at asynkron programmering virkelig gør en forskel, når applikationen har operationer, der tager tid at afslutte og kan fortsætte med at køre andre opgaver, mens de venter på at blive afsluttet. I desktop UI-applikationer, hvor brugervenligheden er afgørende, vil asynkrone metoder hjælpe med at sikre, at brugergrænsefladen forbliver responsiv, selv under tunge operationer.
Når man arbejder med asynkrone operationer, er det også vigtigt at forstå, hvordan operativsystemet og applikationens miljø håndterer trådene, især når det kommer til UI-programmering. I mange UI-rammer er der kun én tråd, der kan kontrollere brugerfladen og reagere på brugerinput. Hvis denne tråd er optaget af en lang operation, vil programmet føles langsomt, og brugeren vil bemærke det. Ved at bruge asynkron programmering kan vi sikre, at applikationen forbliver følsom og reagerer på input, samtidig med at baggrundsoperationer udføres uden at påvirke brugeroplevelsen.
Endelig er det værd at bemærke, at mens asynkrone metoder gør det lettere at arbejde med lange operationer, kræver de stadig en god forståelse af, hvordan ressourcer håndteres og hvordan tråde fungerer i forskellige sammenhænge. At dykke ned i asynkrone mønstre uden en klar forståelse af de grundlæggende koncepter kan føre til vedligeholdelsesproblemer og uventede fejl, især når programmet skal køre under forskellige betingelser eller på tværs af forskellige platforme.
Hvordan man bruger avanceret søgning på sociale medier til at finde præcise oplysninger
Hvordan finder man vej i en fremmed by?
Hvordan forstå de uforudsigelige dynamikker i et ægteskab på tværs af sociale skel
Hvordan navigerer man i en by?
Hvad indebærer det virkelig at være Land Girl under krigen?
Hvordan avancerede ændringer kan skabe nye udfordringer i somatiske øvelser
Hvordan træner man hunde til at pege, lege og hoppe effektivt?
Hvordan implementere "Pull to Refresh" i din app

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