Částečná a nulová bajtová čtení v deflateStream, GZipStream a CryptoStream
Metody Read()
a ReadAsync()
metody v DeflateStreamsystému , GZipStreama CryptoStream již nemusí vracet tolik bajtů, kolik bylo požadováno.
Dříve , DeflateStream, GZipStreama CryptoStream rozbíhají se od typického Stream.Read a Stream.ReadAsync chování následujícími dvěma způsoby, z nichž obě tyto změny řeší:
- Nedokončili operaci čtení, dokud se nedokončila buď vyrovnávací paměť předaná operaci čtení, nebo se nedokončila konec datového proudu.
- Jako streamy obálky nelegovali funkce vyrovnávací paměti nulové délky na datový proud, který zabalí.
Vezměte v úvahu tento příklad, který vytvoří a komprimuje 150 náhodných bajtů. Potom odešle komprimovaná data po jednom bajtu z klienta na server a server dekomprimuje data voláním Read
a vyžádáním všech 150 bajtů.
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;
}
}
}
}
V předchozích verzích rozhraní .NET a .NET Framework ukazuje následující výstup, který Read
byl volána pouze jednou. I když byla data k dispozici k GZipStream
vrácení, Read
byla nucena počkat, až bude požadovaný počet bajtů k dispozici.
Read: 150 bytes
Total received: 150 bytes
Následující výstup v .NET 6 a novějších verzích ukazuje, že Read
se volal několikrát, dokud se nepřijala všechna požadovaná data. I když volání požadavků na Read
150 bajtů bylo možné úspěšně Read
dekomprimovat některé bajty (to znamená všechny bajty, které byly v té době přijaty), a to:
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
Staré chování
Kdy Stream.Read
nebo Stream.ReadAsync
byl volána u některého z ovlivněných typů datových proudů s vyrovnávací pamětí délky N
, operace se nedokončí, dokud:
N
bajty byly načteny z datového proudu, nebo- Základní datový proud vrátil hodnotu 0 z volání ke čtení, což znamená, že nebyla k dispozici žádná další data.
Také když Stream.Read
nebo Stream.ReadAsync
byl volána s vyrovnávací pamětí o délce 0, operace by byla okamžitě úspěšná, někdy bez nutnosti čtení nulové délky ve streamu, který zabalí.
Nové chování
Počínaje rozhraním .NET 6, kdy Stream.Read
nebo Stream.ReadAsync
je volána na jednom z ovlivněných typů datových proudů s vyrovnávací pamětí délky N
, operace se dokončí v těchto případech:
- Alespoň 1 bajt byl načten z datového proudu, nebo
- Základní datový proud vrátí hodnotu 0 z volání ke čtení, což znamená, že nejsou k dispozici žádná další data.
Stream.Read
Pokud nebo Stream.ReadAsync
je volána s vyrovnávací pamětí o délce 0, operace proběhne úspěšně, jakmile bude volání s nenulovou vyrovnávací pamětí úspěšné.
Když zavoláte některou z ovlivněných Read
metod, může čtení splňovat alespoň jeden bajt požadavku bez ohledu na to, kolik jich bylo požadováno, vrátí v tuto chvíli tolik, kolik může.
Zavedená verze
6.0
Důvod změny
Datové proudy se nemusí vrátit z operace čtení, i když se data úspěšně přečetla. To znamenalo, že se nedají snadno použít v žádné obousměrné komunikační situaci, kdy se zprávy menší než velikost vyrovnávací paměti používaly. To může vést k zablokování: Aplikace nemůže číst data z datového proudu, který je nezbytný k pokračování operace. Může také vést k libovolným zpomalením, kdy uživatel nemůže zpracovat dostupná data při čekání na doručení dalších dat.
Ve vysoce škálovatelných aplikacích je také běžné používat nulové bajty jako způsob zpoždění přidělení vyrovnávací paměti, dokud nebude potřeba vyrovnávací paměť. Aplikace může vydat čtení s prázdnou vyrovnávací pamětí a po dokončení čtení by měla být data brzy k dispozici pro využití. Aplikace pak může znovu vydat čtení, tentokrát s vyrovnávací pamětí pro příjem dat. Delegováním na zabalený datový proud, pokud už nejsou k dispozici žádná dekomprimovaná nebo transformovaná data, tyto datové proudy teď zdědí jakékoli takové chování datových proudů, které zabalí.
Doporučená akce
Obecně platí, že kód by měl:
Nepředpokládáme žádné předpoklady týkající se datového proudu
Read
neboReadAsync
operace, která čte tolik, jak bylo požadováno. Volání vrátí počet přečtených bajtů, což může být menší než to, co bylo požadováno. Pokud aplikace závisí na úplném vyplnění vyrovnávací paměti před průběhem, může čtení provést ve smyčce, aby znovu získalo chování.int totalRead = 0; while (totalRead < buffer.Length) { int bytesRead = stream.Read(buffer.AsSpan(totalRead)); if (bytesRead == 0) break; totalRead += bytesRead; }
Počítejte s tím, že datový proud
Read
neboReadAsync
volání nemusí být dokončené, dokud nebude k dispozici alespoň bajt dat pro spotřebu (nebo datový proud dosáhne konce), bez ohledu na to, kolik bajtů bylo požadováno. Pokud aplikace závisí na okamžitém dokončení nulového bajtového čtení bez čekání, může zkontrolovat samotnou délku vyrovnávací paměti a zcela přeskočit volání:int bytesRead = 0; if (!buffer.IsEmpty) { bytesRead = stream.Read(buffer); }
Ovlivněná rozhraní API
- 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)