none
Image aus Cache laden - sehr langsam RRS feed

  • Frage

  • Guten Tag,

    ich habe eine Datenbank, in welcher Bilder binär gespeichert sind.
    Auf einer ASPX-Seite werden die Bilder in einer Listview ausgegeben, etwa 20 pro Seite.

    Damit die Bilder nicht jedes mal aus der Datenbank geladen werden müssen, wollte ich die Bilder im Cache speichern
    und daraus auslesen.

    Für jedes Bild wird die Methode aufgerufen:

        private bool getImage(HttpContext context)
        {
            if (context.Request.QueryString["artId"] != null)
            {
    			//Es wird eine ID übergeben und die dazugehörige ImageID ausgelesen
                Int16 artikelID = Convert.ToInt16(context.Request.QueryString["artId"]);
                string query = "SELECT ImageID FROM artBilderZuordnung WHERE ArtikelID = "+artikelID+";";
                SqlDataReader reader = usr.sqlSelect(query);
                int imageID = 0;
    			
                while (reader.Read())
                {
                    imageID = Convert.ToInt16(reader[0]);
                }
                reader.Dispose();
                reader.Close();
    			
    			//Wenn kein Image zur ID da ist, wird das Default Image geladen(ID=4)
                if (imageID == 0) { imageID = 4; }
                Context.Response.ClearContent();
                
    			//Wenn das Image im Cache ist, soll es daraus gelesen werden
                if (Cache[Convert.ToString(imageID)] != null)
                {
                    System.Drawing.Image imgFromGB = (System.Drawing.Image)Cache[Convert.ToString(imageID)];
                    imgFromGB.Save(Context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
                    return true;
                }
    			//wenn das Image nicht im Cache ist, wird es aus der DB gelesen
                query = "SELECT Image FROM artBilder WHERE ID = " + imageID + ";";
                context.Response.ContentType = "image/jpeg";
                reader = usr.sqlSelect(query);
                object img = new object();
                while (reader.Read())
                {
                    img = reader[0];
                }
                if (img != null)
                {
                    try
                    {
                        MemoryStream ms = new MemoryStream((byte[])img, false);
                        System.Drawing.Image imgFromGB = System.Drawing.Image.FromStream(ms);
    
                        context.Response.ClearContent();
    
                        int width = imgFromGB.Width;
                        int height = imgFromGB.Height;
    					
                        System.Drawing.Image.GetThumbnailImageAbort myCallback = new System.Drawing.Image.GetThumbnailImageAbort(temp);
                        System.Drawing.Image myThumbnail = imgFromGB.GetThumbnailImage(width, height, myCallback, IntPtr.Zero);
                        myThumbnail.Save(Context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
    
    					//Das Bild wird in den Cache geladen
                        Cache[Convert.ToString(imageID) + "akThumb"] = myThumbnail;
                    }
                    catch (Exception)
                    {
                        return false;
                    }
                }
                reader.Close();
            }
            return true;
        }

    Das ganze ist aber sehr langsam, selbst wenn die Bilder aus dem Cache geladen werden. Jeder Bild braucht ca 0,8 Sekunden bis es geladen ist.

    Wie würdet ihr das machen? Ein völlig anderer Prozess oder liegt es an der Programmierung?

    Grüße

    Freitag, 20. April 2012 10:52

Antworten

  • Hi,

    zum einen sollte man natürlich keine WebForm dazu mißbrauchen, Bilder, ... auszuliefern. Dafür gibt es generisches Handler (ASHX). Die machen dasselbe, haben aber den ganzen Overhead für Laden, Rendern, ... der HTML Inhalte nicht.

    Zum anderen könnte es sein, dass Du das Projekt im Debug Modus laufen lässt. Schau mal, ob das Projekt wirklich als Release kompiliert wurde und ob in der web.config ggfs. debug="true" steht. Falls ja, ändere Debug auf "false" und kompilier das Projekt explizit im Release Modus.

    Zum laden von Dateien: Du kannst hier auch Response.TransmitFile( "~/Images/Slide.gif" ) nehmen, das ist erheblich schneller als das vorherige Laden in ein Image Objekt.

    Wichtig ist auch, dass Du alle Objekte, insbesondere was Grafiken, Connections, ... angeht immer schließt und falls eine Dispose Methode vorhanden ist, diese auch aufrufst. Also bspw. image.Dispose(); ... Ansonsten hast Du zum einen gesperrte Resourcen und zum anderen recht schnell ein Speicherproblem.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    • Als Antwort markiert StevS Freitag, 20. April 2012 18:51
    Freitag, 20. April 2012 14:51
    Moderator

Alle Antworten

  • Hi,

    zum einen liest Du bei jedem Request Daten aus der Datenbank. Das sollte doch eigentlich nicht notwendig sein.

    Dann ist der Cache für alle Requests derselbe, wenn mehrere Requests gleichzeitig eingehen, blockiert sich das ggfs. gegenseitig.

    Ich persönlich würde die Bilder nicht im Cache ablegen, sondern die Thumbnails erzeugen und auf der Platte ablegen und diese dann direkt verlinken. IIS kann das sehr gut selbst cachen. 0,8 s pro Bild erscheint mir sehr hoch, es sei denn, die Bilder sind selbst als Thumbnail noch sehr groß.

    Generell ist GetThumbnailImage die schlechteste Wahl, die Qualität der Thumbnails ist nicht wirklich toll. Schau mal, da findest Du eine Alternative.

      http://forums.asp.net/t/1411512.aspx/1

    Und zu guter letzt: Ich würde die Bilder nicht in der Datenbank vorhalten. Wenn überhaupt, nur als FILESTREAM, wobei mir auch das hier nicht sinnvoll erscheint. Gibt es zwingende Gründe, warum Du die Bilder in die Datenbank schreibst?

    ---

    Ein wichtiger Hinweis: Du hast mit solchen Konstrukten

      string query = "SELECT ImageID FROM artBilderZuordnung WHERE ArtikelID = "+artikelID+";";

    ein ziemliches SQL Injection Problem. Arbeite lieber mit Parametern.

    Du konvertierst zwar noch den Wert aus dem QueryString in Int16, das würde hier schon auf Fehler laufen, bei Strings hast Du aber definitiv ein Problem.

    Zum ausprobieren einfach mal 123'456 bei einem solchen Übergabeparameter in der Adresszeile angeben. Alternativ auch mal ' OR 1 = 1 oder auch ';DELETE FROM ...


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community



    Freitag, 20. April 2012 11:12
    Moderator
  • Vielen Dank für Ihre Antwort. Ich werde da wohl noch einiges ändern.

    Es ist eine bestehend Datenbank und dort sind die Bilder bereits gespeichert, ich übernehme die gegebene Struktur.

    Nachdem ich nochmals einiges getestet hatte, ist mir aufgefallen, dass das Problem woanders liegt (oder zusätzlich woanders).
    Selbst wenn ich die Methode ausblende und die Bilder direkt anzeige:

    System.Drawing.Image image = System.Drawing.Image.FromFile(@"slide.gif");
    image.Save(Context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);

    ist es so langsam.

    Deswegen ein Schritt weiter:

    Die Bilder werden auf einer anderen Seite in einem Listview angezeigt (gefütter aus einer sqlDataSource).
    Dort sieht die Stelle wie folgt aus:

    <asp:Image ID="imageVorschau" runat="server" width="60px" Visible="true" ImageUrl='/Image.aspx?artId=<%# "+Eval("ID")+" %>' />

    Dies wird natürlich für jedes Listitem ausgeführt. Kann es daran liegen, dass die Image.aspx so oft aufgerufen wird?

    Danke für die Antworten.

    Freitag, 20. April 2012 12:51
  • Hi,

    zum einen sollte man natürlich keine WebForm dazu mißbrauchen, Bilder, ... auszuliefern. Dafür gibt es generisches Handler (ASHX). Die machen dasselbe, haben aber den ganzen Overhead für Laden, Rendern, ... der HTML Inhalte nicht.

    Zum anderen könnte es sein, dass Du das Projekt im Debug Modus laufen lässt. Schau mal, ob das Projekt wirklich als Release kompiliert wurde und ob in der web.config ggfs. debug="true" steht. Falls ja, ändere Debug auf "false" und kompilier das Projekt explizit im Release Modus.

    Zum laden von Dateien: Du kannst hier auch Response.TransmitFile( "~/Images/Slide.gif" ) nehmen, das ist erheblich schneller als das vorherige Laden in ein Image Objekt.

    Wichtig ist auch, dass Du alle Objekte, insbesondere was Grafiken, Connections, ... angeht immer schließt und falls eine Dispose Methode vorhanden ist, diese auch aufrufst. Also bspw. image.Dispose(); ... Ansonsten hast Du zum einen gesperrte Resourcen und zum anderen recht schnell ein Speicherproblem.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    • Als Antwort markiert StevS Freitag, 20. April 2012 18:51
    Freitag, 20. April 2012 14:51
    Moderator
  • Vielen Dank.

    Mit Ihren Tipps ist der Ladevorgang der Bilder mit dem bloßen Auge gar nicht mehr zu erkennen :)

    Freitag, 20. April 2012 18:53