Streams i .NET

Tags:    .net
Skrevet af Bruger #4522 @ 20.04.2008

Introduktion


I denne artikel skal vi kigge på brugen af streams i .NET. Kodeeksemplerne vil være i C#, men semantisk set er det selvfølgelig det samme i et hvilket som helt .NET sprog, f.eks. VB.NET, Chrome osv.

Streams
Hvad er en stream? Ordet stream er engelsk og betyder "en strøm" - og en .NET stream er netop en strøm af bytes som vi kan bruge til at dirigere vores data rundt med. En stream er blot en abstrakt repræsentation for en enhed som vi kan bruge til at læse data fra eller skrive data til. En stream kan altså både bruges som datakilde og dataforbruger.

Den reelle underliggende enhed der arbejdes med kan være en fil fra dit filsystem, det kan være computerens hukommelse, det kan være en pipe (pipes bruges til kommunikation mellem processer på samme PC og anvender ikke nogen form for netværkstransport), eller det kan være en såkaldt netværks socket. Denne abstraktion som streams er, betyder altså at man bruger samme teknik til at læse/skrive data fra/til både en fil, hukommelse, en netværks socket, en pipe osv.

I denne artikel skal vi kigge lidt på streams i .NET ved at se på de forskellige Stream klasser.

Som læseren nok har bemærket anvender jeg tit det engelske ord stream i stedet for en dansk oversættelse da det blot vil føre til forvirring når læseren søger efter mere information. For afvekslingens skyld burger jeg dog få steder det danske ord strøm i stedet, specielt i ord som datastrøm og bitstrøm.

Streams og .NET


For at forstå streams i .NET starter vi med det store billede. I .NET er hele stream-arkitekturen bygget på tre grundpiller:


  1. Lager-streams

  2. Dekoratør-streams

  3. Stream adapter



Dette er illustreret i følgende figur:



Lager-streams er start- og/eller slutpunktet for data. Det er der vores data kommer fra eller ender. Der kan være tale om en fil man skal læse fra, eller måske skal man skrive til en netværksforbindelse (socket). Du vil altid arbejde med en af disse klasser når du programmere streams i .NET. Udover de fire viste lager-streams blev der i .NET 3.5 tilføjet en lager-stream der bruger pipes: PipeStream. FileStream bruges logisk nok hvis data kommer fra eller skal til en fil. MemoryStream bruger computerens hukommelse som start- og/eller slutpunkt. Og i tilfældet med NetworkStream er der tale om at vi flytter data over en netværksforbindelse (socket).

Dekoratør-stream klasserne bruges til at "dekorere" en lager-stream (hvis navnet får dig til at tænke på Decorator designmønstreret er du helt på rette vej). En af disse klasser bruges til at modificere, eller transformere, en lager-stream. CryptoStream bruges f.eks. hvis man ønsker at kryptere ens stream. GzipStream og DeflateStream bruges begge til komprimering af en stream (forskellen er den algoritme de bruger til komprimering). BufferStream bruges til at holde data i en buffer før det afsendes hvilket optimerer en streams ydeevne.

Når vi bruger Decorator mønstret får vi en masse fordele, herunder at antallet af klasser holdes markant nede (alternativet er at vi skulle arbejde med et stort antal stream klasser med navne som CryptoFileStream, GzipFileSteam, CryptoNetworkStream, GzipNetworkStream osv.), samt at man kan tilføje og fjerne "dekoratørere" ved køretid. Derudover kan man sammenkæde flere dekoratører efter hindanden. Man kan for eksempel først bruge en CryptoStream til at kryptere bitstrømmen, efterfulgt af en BufferStream til at buffer strømmen osv.).

Når vi bruger lager-streams og dekoratør-streams arbejder vi udelukkende med bytes hvilket er både effektivt og fleksibelt. Men hvis vi gør brug af tekst- eller xmlfiler gør det det hele lidt besværligt. I disse tilfælde bruger vi da en stream adapter. Ligesom en dekoratør stream bruges en stream adapter på en lager-stream eller en lager-stream kombineret med én eller flere dekoratør streams som netop forklaret. En stream adapter regnes dog ikke selv for at være en stream da den helt gemmer den underliggende byte-orienterede struktur (for netop at gøre det nemmere for os at håndtere tekst, xml, eller datatyper som int og floats). Ovenstående billede viser ikke alle stream adaptere der er i .NET.

Synkron og asynkron I/O
Som udgangspunkt er alle stream operationer synkrone. Det betyder at den pågældende proces er blokeret indtil I/O handlingen er udført. Synkron I/O er den mest simple måde at håndtere I/O på hvorfor den bruges som udgangspunkt.

Synkron I/O er fint med små filer. Men på større filer, og ved netværk I/O med lav båndbredde/hastighed, giver det en ganske dårlig ydeevne. Dette kan forbedres ved at lade en separat tråd stå for den synkrone I/O, hvilket er tilfældet med asynkron I/O.

Ved asynkron I/O skabes der en tråd for hver I/O forespørgsel. Resultatet er at pågældende program kan fortsætte med at udføre andre opgaver alt imens I/O håndteres asynkront af en anden tråd. Ligeså snart I/O-handlingen er færdig kan den kaldende tråd umiddelbart notificeres herom og få fat i resultatet (hvis der er noget). Dette gør at programmet ikke blokerer ved håndtering af store mængder data, eller ved brug at langsomme netværksforbindelser.


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (2)

User
Bruger #6559 @ 20.04.08 11:17
En meget god artikel. Jeg synes at når du begynder at skrive om MemoryStream, så er det synd du ikke går mere i dybden med det når du alligevel begynder at fortælle lidt om klassen. Der burde du måske bare have undværet det helt og så skrevet i starten at det ikke var med i artiklen. Ellers faldt jeg ikke over det helt store. Godt beskrivende og helt sikkert noget jeg kommer til at bruge. Keep up the good work :)
User
Bruger #4522 @ 20.04.08 15:54
Tak for dine kommentarer Martin. Du har en pointe omkring MemoryStream og jeg vil prøve at uddybe det lidt mere i artiklen.
Du skal være logget ind for at skrive en kommentar.
t