Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Metody Read() i ReadAsync() dla DeflateStream, GZipStream i CryptoStream mogą nie zwracać już tak wielu bajtów, jak żądano.
Wcześniej DeflateStream, GZipStream i CryptoStream różniły się od typowych zachowań Stream.Read i Stream.ReadAsync na następujące dwa sposoby, które obie są uwzględnione w tej zmianie:
- Operacja odczytu nie została zakończona, dopóki bufor przekazany do operacji odczytu nie został całkowicie wypełniony lub nie osiągnięto końca strumienia.
- Jako strumienie otoki nie delegowali funkcji buforu o zerowej długości do strumienia, który opakowują.
Rozważmy ten przykład, który tworzy i kompresuje 150 bajtów losowych. Następnie wysyła skompresowane dane pojedynczo z klienta do serwera, a serwer dekompresuje dane przez wywołanie Read i zażądanie wszystkich 150 bajtów.
using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
internal class Program
{
private static async Task Main()
{
// Connect two sockets and wrap a stream around each.
using (Socket listener = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
using (Socket client = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
listener.Listen(int.MaxValue);
client.Connect(listener.LocalEndPoint!);
using (Socket server = listener.Accept())
{
var clientStream = new NetworkStream(client, ownsSocket: true);
var serverStream = new NetworkStream(server, ownsSocket: true);
// Create some compressed data.
var compressedData = new MemoryStream();
using (var gz = new GZipStream(compressedData, CompressionLevel.Fastest, leaveOpen: true))
{
byte[] bytes = new byte[150];
new Random().NextBytes(bytes);
gz.Write(bytes, 0, bytes.Length);
}
// Trickle it from the client stream to the server.
Task sendTask = Task.Run(() =>
{
foreach (byte b in compressedData.ToArray())
{
clientStream.WriteByte(b);
}
clientStream.Dispose();
});
// Read and decompress all the sent bytes.
byte[] buffer = new byte[150];
int total = 0;
using (var gz = new GZipStream(serverStream, CompressionMode.Decompress))
{
int numRead = 0;
while ((numRead = gz.Read(buffer.AsSpan(numRead))) > 0)
{
total += numRead;
Console.WriteLine($"Read: {numRead} bytes");
}
}
Console.WriteLine($"Total received: {total} bytes");
await sendTask;
}
}
}
}
W poprzednich wersjach programu .NET i .NET Framework następujące dane wyjściowe pokazały, że Read było wywoływane tylko raz. Mimo że dane były dostępne, aby GZipStream mogło zwrócić dane, Read musiało czekać, aż żądana liczba bajtów będzie dostępna.
Read: 150 bytes
Total received: 150 bytes
W programie .NET 6 i nowszych wersjach następujące dane wyjściowe pokazują, że Read było wywoływane wiele razy do momentu odebrania wszystkich żądanych danych. Mimo że wywołanie do Read żąda 150 bajtów, każde wywołanie metody Read było w stanie pomyślnie zdekompresować niektóre bajty (czyli wszystkie bajty, które zostały odebrane w tym czasie) do zwrócenia, i tak było:
Read: 1 bytes
Read: 101 bytes
Read: 4 bytes
Read: 4 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 3 bytes
Read: 2 bytes
Read: 3 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 1 bytes
Read: 2 bytes
Read: 1 bytes
Read: 1 bytes
Read: 1 bytes
Read: 2 bytes
Read: 1 bytes
Read: 1 bytes
Read: 2 bytes
Read: 1 bytes
Read: 1 bytes
Read: 2 bytes
Total received: 150 bytes
Stare zachowanie
Gdy Stream.Read lub Stream.ReadAsync wywołano dla jednego z typów strumieni, których dotyczy problem z buforem o długości N, operacja nie została ukończona do momentu:
-
Nbajtów odczytano ze strumienia lub - Strumień źródłowy zwrócił wartość 0 z wywołania do odczytu, co oznacza, że nie było już więcej danych.
Ponadto, gdy wywołano Stream.Read lub Stream.ReadAsync z buforem o długości 0, operacja kończyła się natychmiast powodzeniem, czasami bez wykonywania odczytu o zerowej długości na strumieniu, który opakowuje.
Nowe zachowanie
Począwszy od platformy .NET 6, gdy Stream.Read lub Stream.ReadAsync jest wywoływana na jednym z typów strumieni, których dotyczy problem, z buforem o długości N, operacja kończy się, gdy:
- Co najmniej 1 bajt został odczytany ze strumienia lub
- Strumień bazowy zwraca wartość 0 z wywołania odczytu, co oznacza, że nie ma już więcej danych.
Ponadto gdy wywołanie Stream.Read lub Stream.ReadAsync nastąpi z buforem o długości 0, operacja powiedzie się, gdy wywołanie z buforem o długości różnej od zera zakończyłoby się powodzeniem.
Jeśli wywołasz jedną z metod, których dotyczy Read, i jeśli odczyt może zwrócić co najmniej jeden bajt z żądania, to niezależnie od liczby żądanych bajtów zwróci tyle, ile może w tej chwili.
Wersja, w której wprowadzono
6.0
Przyczyna zmiany
Operacje odczytu na strumieniach mogły nie zostać zakończone, nawet jeśli dane zostały pomyślnie odczytane. Oznaczało to, że nie mogły być łatwo używane w żadnej sytuacji komunikacji dwukierunkowej, w której używane są komunikaty mniejsze niż rozmiar buforu. Może to prowadzić do zakleszczeń: aplikacja nie może odczytać ze strumienia danych niezbędnych do kontynuowania operacji. Może to również prowadzić do nieprzewidywalnych spowolnień, a odbiorca nie może przetworzyć dostępnych danych podczas oczekiwania na nadejście większej liczby danych.
Ponadto w wysoce skalowalnych aplikacjach często używa się odczytów zerobajtowych jako sposobu opóźniania alokacji buforu do momentu, gdy bufor będzie potrzebny. Aplikacja może wykonać operację odczytu z pustym buforem, a po zakończeniu odczytu dane powinny być wkrótce dostępne do odczytu. Następnie aplikacja może ponownie wykonać operację odczytu, tym razem z buforem w celu odebrania danych. Delegując do opakowanego strumienia, jeśli nie są dostępne już zdekompresowane lub przekształcone dane, strumienie te dziedziczą teraz wszelkie takie zachowania strumieni, które opakowują.
Zalecane działanie
Ogólnie rzecz biorąc, kod powinien:
Nie należy zakładać, że operacja odczytu ze strumienia
ReadlubReadAsyncodczyta tyle bajtów, ile zażądano. Wywołanie zwraca liczbę odczytanych bajtów, która może być mniejsza niż liczba żądanych bajtów. Jeśli aplikacja zależy od całkowitego wypełnienia buforu, zanim przejdzie dalej, może wykonać odczyt w pętli, aby odzyskać zachowanie.int totalRead = 0; while (totalRead < buffer.Length) { int bytesRead = stream.Read(buffer.AsSpan(totalRead)); if (bytesRead == 0) break; totalRead += bytesRead; }Począwszy od .NET 7, można również użyć Stream.ReadExactly lub Stream.ReadExactlyAsync, aby odczytać dokładną liczbę bajtów żądanych do odczytania do buforu:
stream.ReadExactly(buffer);Podczas odczytywania danych tekstowych ze strumienia, takich jak odczytywanie odszyfrowanego tekstu z obiektu
CryptoStream, rozważ użycie StreamReader, aby odczytać dane do końca zamiast ręcznego odczytywania do tablic bajtowych:using var reader = new StreamReader(cryptoStream); string plainText = reader.ReadToEnd();Spodziewaj się, że wywołanie
ReadlubReadAsyncna strumieniu może nie zostać ukończone, dopóki co najmniej jeden bajt danych nie będzie dostępny do odczytu (lub strumień osiągnie swój koniec), niezależnie od liczby żądanych bajtów. Jeśli aplikacja zależy od zakończenia odczytu zero bajtów natychmiast bez oczekiwania, może sprawdzić długość buforu i całkowicie pominąć wywołanie:int bytesRead = 0; if (!buffer.IsEmpty) { bytesRead = stream.Read(buffer); }
Interfejsy API, których dotyczy problem
- System.IO.Compression.DeflateStream.Read
- System.IO.Compression.DeflateStream.ReadAsync
- System.IO.Compression.DeflateStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object)
- System.IO.Compression.GZipStream.Read
- System.IO.Compression.GZipStream.ReadAsync
- System.IO.Compression.GZipStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object)
- System.Security.Cryptography.CryptoStream.Read
- System.Security.Cryptography.CryptoStream.ReadAsync
- System.Security.Cryptography.CryptoStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object)