DeflateStream, GZipStream ve CryptoStream'de kısmi ve sıfır bayt okumaları

Read(), GZipStreamve CryptoStream üzerindeki DeflateStreamve ReadAsync() yöntemleri artık istendiği kadar bayt döndürmeyebilir.

Daha önce, DeflateStream, GZipStreamve CryptoStream , bu değişikliğin ele aldığı aşağıdaki iki yöntemle tipik Stream.Read ve Stream.ReadAsync davranıştan ayrıştırıldı:

  • Okuma işlemine geçirilen arabellek tamamen doldurulana veya akışın sonuna ulaşılana kadar okuma işlemini tamamlamadılar.
  • Sarmalayıcı akışları olarak, sarmaladıkları akışa sıfır uzunluklu arabellek işlevselliğini devretmediler.

150 rastgele bayt oluşturan ve sıkıştıran bu örneği düşünün. Ardından sıkıştırılmış verileri istemciden sunucuya birer birer gönderir ve sunucu 150 bayt'ın tümünü çağırarak Read ve isteyerek verilerin sıkıştırmasını kaldırır.

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;
            }
        }
    }
}

.NET ve .NET Framework'ün önceki sürümlerinde, aşağıdaki çıkışta yalnızca bir kez çağrıldığı gösterilmiştir Read . Veriler döndürülmek üzere GZipStream kullanılabilir olsa da, Read istenen bayt sayısı kullanılabilir olana kadar beklemeye zorlandı.

Read: 150 bytes
Total received: 150 bytes

.NET 6 ve sonraki sürümlerde, aşağıdaki çıktı, istenen tüm veriler alınana kadar birden çok kez çağrıldığını gösterirRead. Çağrısı 150 bayt istemesine ReadRead rağmen, her çağrısı döndürülmek üzere bazı baytların (yani o sırada alınan tüm baytların) sıkıştırmasını başarıyla kaldırabildi ve şöyle oldu:

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

Eski davranış

Etkilenen akış türlerinden birinde uzunluğu Narabellek ile çağrıldığında Stream.Read veya Stream.ReadAsync çağrıldığında, işlem aşağıdakilere kadar tamamlanmıyordu:

  • N baytlar akıştan okunmuştu veya
  • Temel alınan akış, okumasına yapılan bir çağrıdan 0 döndürdü ve artık kullanılabilir veri olmadığını gösteriyordu.

Ayrıca, 0 uzunluğunda bir arabellekle çağrıldığında veya Stream.ReadAsync çağrıldığındaStream.Read, işlem bazen kaydırıldığı akışta sıfır uzunluklu bir okuma yapmadan hemen başarılı olur.

Yeni davranış

.NET 6'dan başlayarak, etkilenen akış türlerinden birinde uzunluğu Narabellek ile çağrıldığında Stream.Read veya Stream.ReadAsync çağrıldığında işlem şu durumlarda tamamlanır:

  • Akıştan en az 1 bayt okundu veya
  • Temel alınan akış, okuma çağrısından 0 döndürür ve artık kullanılabilir veri olmadığını gösterir.

Ayrıca, uzunluğu 0 olan bir arabellekle çağrıldığında veya Stream.ReadAsync çağrıldığındaStream.Read, sıfır olmayan arabelleğe sahip bir çağrı başarılı olduğunda işlem başarılı olur.

Etkilenen Read yöntemlerden birini çağırdığınızda, okuma isteğin en az bir baytını karşılayabilirse, kaç tane istendiğine bakılmaksızın, o anda döndürebildiği kadar döndürür.

Sürüm kullanıma sunulmuştur

6,0

Değişiklik nedeni

Veriler başarıyla okunmuş olsa bile akışlar bir okuma işleminden döndürülmemiş olabilir. Bu, arabellek boyutundan daha küçük iletilerin kullanıldığı herhangi bir çift yönlü iletişim durumunda kullanılmaya hazır olamayacağı anlamına geliyordu. Bu kilitlenmelere yol açabilir: Uygulama, işleme devam etmek için gerekli olan verileri akıştan okuyamıyor. Ayrıca, tüketici daha fazla verinin gelmesini beklerken kullanılabilir verileri işleyemediği için rastgele yavaşlamalara yol açabilir.

Ayrıca, yüksek oranda ölçeklenebilir uygulamalarda, arabellek ayırmayı arabellek gerekli olana kadar geciktirme yöntemi olarak sıfır bayt okumaları kullanmak yaygın bir yöntemdir. Bir uygulama boş bir arabelleğe sahip bir okuma verebilir ve bu okuma tamamlandığında, verilerin yakında kullanılabilmesi gerekir. Uygulama daha sonra okuma işlemini yeniden verebilir ve bu kez verileri almak için bir arabellek kullanabilir. Zaten sıkıştırılmış veya dönüştürülmüş veri yoksa sarmalanmış akışa temsilci atayarak, bu akışlar artık sarmaladıkları akışların bu tür davranışlarını devralır.

Genel olarak kod şunları yapmalıdır:

  • Akış veya ReadAsync işlem okuması hakkında Read istenen kadar varsayımda bulunmayın. Çağrı, istenenden daha az olabilecek okunan bayt sayısını döndürür. Bir uygulama ilerlemeden önce arabellek tamamen doldurulmasına bağımlıysa, davranışı yeniden kazanmak için bir döngüde okuma gerçekleştirebilir.

    int totalRead = 0;
    while (totalRead < buffer.Length)
    {
        int bytesRead = stream.Read(buffer.AsSpan(totalRead));
        if (bytesRead == 0) break;
        totalRead += bytesRead;
    }
    
  • Bir akışın Read veya ReadAsync çağrının, istenen bayt sayısına bakılmaksızın en az bir bayt veri tüketim için kullanılabilir duruma gelene kadar (veya akış sonuna ulaşana kadar) tamamlanmayabileceğini bekleyin. Bir uygulama beklemeden hemen tamamlayan sıfır bayt okuma işlemine bağımlıysa arabellek uzunluğunu denetleyerek çağrıyı tamamen atlayabilir:

    int bytesRead = 0;
    if (!buffer.IsEmpty)
    {
        bytesRead = stream.Read(buffer);
    }
    

Etkilenen API’ler