Image.Save (.. ) แสดงข้อยกเว้น GDI + เนื่องจากสตรีมหน่วยความจำถูกปิด


109

ฉันมีข้อมูลไบนารีที่ต้องการบันทึกเป็นรูปภาพ เมื่อฉันพยายามบันทึกภาพมันจะเกิดข้อยกเว้นหากสตรีมหน่วยความจำที่ใช้สร้างภาพถูกปิดก่อนบันทึก เหตุผลที่ฉันทำเช่นนี้เพราะฉันสร้างภาพแบบไดนามิกและด้วยเหตุนี้ .. ฉันจำเป็นต้องใช้สตรีมหน่วยความจำ

นี่คือรหัส:

[TestMethod]
public void TestMethod1()
{
    // Grab the binary data.
    byte[] data = File.ReadAllBytes("Chick.jpg");

    // Read in the data but do not close, before using the stream.
    Stream originalBinaryDataStream = new MemoryStream(data);
    Bitmap image = new Bitmap(originalBinaryDataStream);
    image.Save(@"c:\test.jpg");
    originalBinaryDataStream.Dispose();

    // Now lets use a nice dispose, etc...
    Bitmap2 image2;
    using (Stream originalBinaryDataStream2 = new MemoryStream(data))
    {
        image2 = new Bitmap(originalBinaryDataStream2);
    }

    image2.Save(@"C:\temp\pewpew.jpg"); // This throws the GDI+ exception.
}

ใครมีคำแนะนำเกี่ยวกับวิธีบันทึกภาพโดยปิดสตรีมหรือไม่? ฉันไม่สามารถพึ่งพานักพัฒนาได้ แต่อย่าลืมปิดสตรีมหลังจากบันทึกภาพแล้ว ในความเป็นจริงนักพัฒนาจะไม่มีความคิดว่ารูปภาพถูกสร้างขึ้นโดยใช้สตรีมหน่วยความจำ (เพราะมันเกิดขึ้นในโค้ดอื่น ๆ ที่อื่น)

ฉันสับสนจริงๆ :(


1
ผมได้รับความคิดเห็นนี้จาก @HansPassant อีกคำถาม คุณจะได้รับข้อยกเว้นนี้ทุกครั้งที่ตัวแปลงสัญญาณมีปัญหาในการเขียนไฟล์ คำสั่งการดีบักที่ดีที่จะเพิ่มคือ System.IO.File.WriteAllText (path, "test") ก่อนการเรียก Save () จะตรวจสอบความสามารถพื้นฐานในการสร้างไฟล์ ตอนนี้คุณจะได้รับข้อยกเว้นที่ดีที่จะบอกคุณว่าคุณทำอะไรผิด
Juan Carlos Oropeza

คุณควร image2 บันทึกภายในusingบล็อก ฉันคิดว่าoriginalBinaryDataStream2 มันถูกกำจัดโดยอัตโนมัติเมื่อสิ้นสุดการใช้งาน และนั่นจะทำให้เกิดข้อยกเว้น
taynguyen

คำตอบ:


172

เนื่องจากเป็น MemoryStream คุณจึงไม่จำเป็นต้องปิดสตรีม แต่จะไม่มีอะไรเลวร้ายเกิดขึ้นหากคุณไม่ทำแม้ว่าจะเห็นได้ชัดว่าเป็นแนวทางปฏิบัติที่ดีในการกำจัดสิ่งที่ใช้แล้วทิ้ง (ดูคำถามเพิ่มเติมเกี่ยวกับเรื่องนี้)

อย่างไรก็ตามคุณควรกำจัด Bitmap - และนั่นจะเป็นการปิดสตรีมให้คุณ โดยทั่วไปเมื่อคุณให้สตรีมตัวสร้างบิตแมปสตรีมจะ "เป็นเจ้าของ" สตรีมและคุณไม่ควรปิดสตรีม ตามที่เอกสารสำหรับตัวสร้างนั้นกล่าวว่า:

คุณต้องเปิดสตรีมไว้ตลอดอายุการใช้งานของ Bitmap

ฉันไม่พบเอกสารใด ๆ ที่สัญญาว่าจะปิดสตรีมเมื่อคุณกำจัดบิตแมป แต่คุณควรจะตรวจสอบได้ง่ายพอสมควร


2
สุดยอด! นั่นเป็นการตอบกลับที่ดีของจอน ทำให้เกิดความรู้สึกที่สมบูรณ์แบบ (และฉันพลาดบิตเกี่ยวกับสตรีมในเอกสาร) สองยกนิ้ว! ฉันจะรายงานกลับเมื่อฉันให้มันไป :)
Pure.Krome

มีความคิดเห็นเกี่ยวกับเรื่องนี้อย่างไรหากเราต้องการปฏิบัติตามกฎ CA2000? (msdn.microsoft.com/en-us/library/ms182289.aspx)
Patrick Szalapski

@Patrick: มันใช้ไม่ได้ - คุณได้โอนความเป็นเจ้าของทรัพยากรโดยพื้นฐานแล้ว สิ่งที่ใกล้เคียงที่สุดที่คุณสามารถทำได้คือการสร้างกระดาษห่อหุ้ม "NonClosingStream" ซึ่งจะละเว้นการโทรยกเลิก ฉันคิดว่าฉันอาจมีหนึ่งใน MiscUtil - ไม่แน่ใจ ...
Jon Skeet

ขอบคุณข้อมูล @Jon. สำหรับฉันด้วยเหตุผลแปลก ๆ มันใช้งานได้แม้จะมีการทิ้ง () ในสภาพแวดล้อม dev ในพื้นที่ แต่ไม่ได้ทำงานในการผลิต
Oxon

94

เกิดข้อผิดพลาดทั่วไปใน GDI + อาจเป็นผลมาจากเส้นทางการบันทึกไม่ถูกต้อง ! ฉันใช้เวลาครึ่งวันเพื่อสังเกตว่า ดังนั้นตรวจสอบให้แน่ใจว่าคุณได้ตรวจสอบเส้นทางอีกครั้งเพื่อบันทึกภาพด้วย


4
C\Users\mason\Desktop\pic.pngฉันดีใจที่ฉันเห็นนี้เส้นทางของฉันคือ ลำไส้ใหญ่หายไป! ฉันจะใช้เวลาตลอดไปก่อนที่ฉันจะสังเกตเห็นสิ่งนั้น
ก่ออิฐ

4
ไม่ถูกต้องยังหมายความว่าไม่มีโฟลเดอร์ที่คุณต้องการบันทึกภาพ
Roemer

14

บางทีอาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าหากไม่มีไดเร็กทอรี C: \ Temp จะทำให้เกิดข้อยกเว้นนี้แม้ว่าสตรีมของคุณจะยังคงมีอยู่ก็ตาม


+1 ข้อยกเว้นนี้ดูเหมือนจะเกิดขึ้นในหลายสถานการณ์ เส้นทางที่ไม่ถูกต้องคือเส้นทางที่ฉันพบในวันนี้
Kirk Broadhurst

4

ฉันมีปัญหาเดียวกัน แต่จริงๆแล้วสาเหตุคือแอปพลิเคชันไม่ได้รับอนุญาตให้บันทึกไฟล์บน C เมื่อฉันเปลี่ยนเป็น "D: \ .. " รูปภาพได้รับการบันทึกแล้ว


2

คัดลอก Bitmap คุณต้องเปิดสตรีมไว้ตลอดอายุการใช้งานของบิตแมป

เมื่อวาดภาพ: System.Runtime.InteropServices.ExternalException: เกิดข้อผิดพลาดทั่วไปใน GDI

    public static Image ToImage(this byte[] bytes)
    {
        using (var stream = new MemoryStream(bytes))
        using (var image = Image.FromStream(stream, false, true))
        {
            return new Bitmap(image);
        }
    }

    [Test]
    public void ShouldCreateImageThatCanBeSavedWithoutOpenStream()
    {
        var imageBytes = File.ReadAllBytes("bitmap.bmp");

        var image = imageBytes.ToImage();

        image.Save("output.bmp");
    }

1
ไม่ได้ผลอย่างแน่นอน ในโค้ดของคุณใน ToImage () "image" ในเครื่องจะมี. RawFormat ของไฟล์ต้นฉบับที่ถูกต้อง (jpeg หรือ png ฯลฯ ) ในขณะที่ค่าส่งคืนของ ToImage () จะมี. RawFormat MemoryBmp โดยไม่คาดคิด
Patrick Szalapski

ไม่แน่ใจว่าRawFormatเรื่องมากแค่ไหน หากคุณต้องการที่จะใช้ที่เรียกดูได้จากที่ไหนสักแห่งวัตถุไปพร้อมกัน แต่โดยทั่วไปบันทึกเป็นชนิดใด ๆ ก็ตามที่คุณต้องการจริงที่จะมี
Nyerguds

2

คุณสามารถลองสร้างสำเนาบิตแมปอื่นได้:

using (var memoryStream = new MemoryStream())
{
    // write to memory stream here

    memoryStream.Position = 0;
    using (var bitmap = new Bitmap(memoryStream))
    {
        var bitmap2 = new Bitmap(bitmap);
        return bitmap2;
    }
}

2

ข้อผิดพลาดนี้เกิดขึ้นกับฉันเมื่อฉันพยายามจาก Citrix โฟลเดอร์รูปภาพถูกตั้งค่าเป็น C: \ ในเซิร์ฟเวอร์ซึ่งฉันไม่มีสิทธิ์ เมื่อย้ายโฟลเดอร์รูปภาพไปยังไดรฟ์ที่แชร์ข้อผิดพลาดก็หายไป


1

เกิดข้อผิดพลาดทั่วไปใน GDI + อาจเกิดขึ้นได้เนื่องจากปัญหาเส้นทางการจัดเก็บภาพฉันได้รับข้อผิดพลาดนี้เนื่องจากเส้นทางการจัดเก็บของฉันยาวเกินไปฉันแก้ไขปัญหานี้โดยการจัดเก็บภาพในเส้นทางที่สั้นที่สุดก่อนแล้วย้ายไปยังตำแหน่งที่ถูกต้องด้วยเทคนิคการจัดการเส้นทางยาว


1

ฉันได้รับข้อผิดพลาดนี้เนื่องจากการทดสอบอัตโนมัติที่ฉันกำลังดำเนินการพยายามจัดเก็บสแนปชอตลงในโฟลเดอร์ที่ไม่มีอยู่ หลังจากที่ฉันสร้างโฟลเดอร์ข้อผิดพลาดได้รับการแก้ไข


0

วิธีแก้ปัญหาแปลก ๆ อย่างหนึ่งที่ทำให้รหัสของฉันทำงานได้ เปิดภาพด้วยสีและบันทึกเป็นไฟล์ใหม่ด้วยรูปแบบเดียวกัน (.jpg) ตอนนี้ลองกับไฟล์ใหม่นี้และใช้งานได้ อธิบายอย่างชัดเจนว่าไฟล์อาจเสียหายในบางครั้ง สิ่งนี้จะช่วยได้ก็ต่อเมื่อโค้ดของคุณมีการแก้ไขข้อบกพร่องอื่น ๆ ทั้งหมด


0

มันยังปรากฏขึ้นพร้อมกับฉันเมื่อฉันพยายามบันทึกภาพลงในเส้นทาง

C:\Program Files (x86)\some_directory

และ.exeไม่ได้ดำเนินการเพื่อเรียกใช้ในฐานะผู้ดูแลระบบฉันหวังว่านี่อาจช่วยคนที่มีปัญหาเดียวกันได้เช่นกัน


0

สำหรับฉันรหัสด้านล่างล้มเหลวกับA generic error occurred in GDI+บรรทัดที่บันทึกเป็นMemoryStreamไฟล์. รหัสกำลังทำงานบนเว็บเซิร์ฟเวอร์และฉันแก้ไขโดยการหยุดและเริ่ม Application Pool ที่กำลังเรียกใช้ไซต์

ต้องมีข้อผิดพลาดภายใน GDI +

    private static string GetThumbnailImageAsBase64String(string path)
    {
        if (path == null || !File.Exists(path))
        {
            var log = ContainerResolver.Container.GetInstance<ILog>();
            log.Info($"No file was found at path: {path}");
            return null;
        }

        var width = LibraryItemFileSettings.Instance.ThumbnailImageWidth;

        using (var image = Image.FromFile(path))
        {
            using (var thumbnail = image.GetThumbnailImage(width, width * image.Height / image.Width, null, IntPtr.Zero))
            {
                using (var memoryStream = new MemoryStream())
                {
                    thumbnail.Save(memoryStream, ImageFormat.Png); // <= crash here 
                    var bytes = new byte[memoryStream.Length];
                    memoryStream.Position = 0;
                    memoryStream.Read(bytes, 0, bytes.Length);
                    return Convert.ToBase64String(bytes, 0, bytes.Length);
                }
            }
        }
    }

0

ฉันเจอข้อผิดพลาดนี้เมื่อพยายามแก้ไขภาพง่ายๆในแอพ WPF

การตั้งค่า Source ขององค์ประกอบภาพเป็นบิตแมปจะป้องกันการบันทึกไฟล์ แม้แต่การตั้งค่า Source = null ดูเหมือนจะไม่ปล่อยไฟล์

ตอนนี้ฉันไม่เคยใช้รูปภาพเป็นองค์ประกอบที่มาของรูปภาพดังนั้นฉันจึงสามารถเขียนทับได้หลังจากแก้ไขแล้ว!

แก้ไข

หลังจากได้ยินเกี่ยวกับคุณสมบัติ CacheOption (ขอบคุณ @Nyerguds) ฉันพบวิธีแก้ปัญหา: ดังนั้นแทนที่จะใช้ตัวสร้างบิตแมปฉันต้องตั้งค่า Uri หลังจากการตั้งค่าCacheOption BitmapCacheOption.OnLoad( Image1ด้านล่างคือImageองค์ประกอบWpf )

แทน

Image1.Source = new BitmapImage(new Uri(filepath));

ใช้:

var image = new BitmapImage();
image.BeginInit();
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(filepath);
image.EndInit();
Image1.Source = image;

ดูสิ่งนี้: WPF Image Caching


1
อิมเมจ WPF มีพารามิเตอร์เฉพาะBitmapCacheOption.OnLoadเพื่อตัดการเชื่อมต่อจากแหล่งโหลด
Nyerguds

ขอบคุณ @Nyerguds จนกระทั่งความคิดเห็นของคุณฉันไม่สามารถถามคำถามที่ถูกต้องได้
mkb

0

ลองใช้รหัสนี้:

static void Main(string[] args)
{
    byte[] data = null;
    string fullPath = @"c:\testimage.jpg";

    using (MemoryStream ms = new MemoryStream())
    using (Bitmap tmp = (Bitmap)Bitmap.FromFile(fullPath))
    using (Bitmap bm = new Bitmap(tmp))
    {
        bm.SetResolution(96, 96);
        using (EncoderParameters eps = new EncoderParameters(1))
        {   
            eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
            bm.Save(ms, GetEncoderInfo("image/jpeg"), eps);
        }

        data = ms.ToArray();
    }

    File.WriteAllBytes(fullPath, data);
}

private static ImageCodecInfo GetEncoderInfo(string mimeType)
{
        ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();

        for (int j = 0; j < encoders.Length; ++j)
        {
            if (String.Equals(encoders[j].MimeType, mimeType, StringComparison.InvariantCultureIgnoreCase))
                return encoders[j];
        }
    return null;
}

0

ฉันใช้ตัวประมวลผลภาพเพื่อปรับขนาดภาพและวันหนึ่งฉันได้รับข้อยกเว้น "เกิดข้อผิดพลาดทั่วไปใน GDI +"

หลังจากค้นหาในขณะที่ฉันพยายามรีไซเคิลแอปพลิเคชันพูลและบิงโกมันใช้งานได้ ดังนั้นฉันจึงบันทึกไว้ที่นี่หวังว่ามันจะช่วยได้;)

ไชโย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.