Hádanka - Trable s GZip kompresí

Tomáš Herceg       16. 3. 2014       C#, .NET       5478 zobrazení

Následující funkce dostane objekt, serializuje ho do JSONu a výsledek prožene GZip kompresí.

Prozradím, že Flush na jsonWriter nezafunguje a ve výsledku chybí pár bajtů z konce těch dat. Otázkou je, proč se to děje, a jak to opravit?

P.S. Není to bug ve Frameworku.

        public string SerializeGame(Game game)
        {
            var serializer = new JsonSerializer() { TypeNameHandling = TypeNameHandling.All };

            using (var ms = new MemoryStream())
            {
                using (var gzip = new GZipStream(ms, CompressionMode.Compress))
                {
                    using (var writer = new StreamWriter(gzip))
                    {
                        using (var jsonWriter = new JsonTextWriter(writer))
                        {
                            serializer.Serialize(jsonWriter, game);

                            jsonWriter.Flush();             // tenhle Flush nefunguje, ale proč?
                            return Convert.ToBase64String(ms.ToArray());
                        }
                    }
                }
            }
        }

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

return treba az neskor

Ja by som return Convert.ToBase64String(ms.ToArray()); dal az pred ukoncenim posledneho using, cize takto:

public string SerializeGame(Game game)
{
    var serializer = new JsonSerializer() { TypeNameHandling = TypeNameHandling.All };
 
    using (var ms = new MemoryStream())
    {
        using (var gzip = new GZipStream(ms, CompressionMode.Compress))
        {
            using (var writer = new StreamWriter(gzip))
            {
                using (var jsonWriter = new JsonTextWriter(writer))
                {
                    serializer.Serialize(jsonWriter, game);
 
                    jsonWriter.Flush();             // tenhle Flush nefunguje, ale proč?
                }
            }
        }

        return Convert.ToBase64String(ms.ToArray());
    }
}
nahlásit spamnahlásit spam 0 odpovědětodpovědět

Stejné řešení jsem tu již navrhoval.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tohle fungovat nebude, jakmile GZipStream uzavřete, uzavře automaticky i MemoryStream, a tudíž nepůjde zavolat ToArray(). Musí se ještě do konstruktoru GZipStreamu nastavit parametr leaveOpen na true, pak by to fungovalo.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Pokud by to nebyl MemoryStream nebo by jsme ho chtěli číst pomoci GetBuffer (nebo nastavením pozice na 0 a čtením přes StreamReader) tak souhlas, že by tam to nastavení leaveOpen na true v konstruktoru GZipStream být muselo, aby k uzavření vnějšího streamu nedošlo (a obecně je to asi o trochu čistší pro budoucí úpravy kódu).

Nicméně v tomto konkrétním případě by to fungovalo i bez toho leaveOpen, protože ToArray i na disposnutým MemoryStreamu funguje.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Flush

Kdysi jsem řešil něco podobného a vyřešil jsem to zavoláním Flush() u všech těch streamů...

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tak přesně tohle nepomůže.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

GZipStream.Flush has no effects

Co dělá metoda JsonTextWriter.Flush přesně nevím, ale čekal bych, že by mohla volat GZipStream.Flush.

Problém bude ale asi v tom, že metoda GZipStream.Flush nedělá nic (viz http://msdn.microsoft.com/en-us/library/... a před voláním ms.ToArray() bude potřeba volat Dispose na GZipStream, aby došlo k dozapsání ZIP streamu:

The current implementation of this method does not flush the internal buffer. The internal buffer is flushed when the object is disposed.

Tj. mělo by například stačit posunout řádekreturn Convert.ToBase64String(ms.ToArray());

až těsně za blok using (var gzip = ... (a žádný Flush nikde nevolat).

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Viděl bych to podobně

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Ta odpověď je tu několikrát a první bych také vyzkoušel zavolat flush a eventuálně Dispose na streamech, které měli způsobit zápis do Memory Streamu.

První bych osobně uzavřel všechny streamy jen pro zápis a teprve pak četl výsledke. Flush prostě není kaskádová operace.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Bingo. Flush by kaskádové být mělo, ale ani když poctivě flushnete všechny ty streamy, fungovat to nebude. U GZipStreamu Flush nic nedělá, dokud ten stream celý nezavřete - je to kvůli tomu, jak GZIP funguje uvnitř, ono to zkrátka technicky nejde.

Jediná možnost je tedy nastavit v konstruktoru leaveOpen na true, díky čemuž můžete disposnout ten GZipStream, aniž by to automaticky disposnulo ten MemoryStream, který ještě potřebujete přečíst.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Dispose

Není to kvůli tomu, že samotný stream ještě není uzavřen a data se tak nenačtou celá, respektive jejich konec zůstane chybí, dokud nedojde k uzavření streamu nebo dispose? Tuším, že jsem něco podobného řešil s kolegou a mám pocit, že existovala konstrukce:

public string SerializeGame(Game game)
{
    var serializer = new JsonSerializer() { TypeNameHandling = TypeNameHandling.All };
 
    using (var ms = new MemoryStream())
    {
        using (var gzip = new GZipStream(ms, CompressionMode.Compress, true))
        {
            using (var writer = new StreamWriter(gzip))
            {
                using (var jsonWriter = new JsonTextWriter(writer))
                {
                    serializer.Serialize(jsonWriter, game);
 
                    jsonWriter.Flush();             // tenhle Flush nefunguje, ale proč?
                    return Convert.ToBase64String(ms.ToArray());
                }
            }
        }
    }
}

která toto řešila..ale jistý si nejsem.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Uvedení true do třetího parametru (leaveOpen) v konstruktoru GZipStream způsobí, že při volání GZipStream.Dispose nebude uzavřen "vnější" stream - v tomto případě MemoryStream. To na volání ms.ToArray() nemá vliv, ToArray lze volat i na disposed MemoryStreamu. Mělo by to vliv na ms.GetBuffer(), ten na disposed MemoryStreamu volat nelze.

S tím, kdy dojde k "dozapsání" ZIP streamu, parametr leaveOpen nesouvisí.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback