【问题标题】:how to prevent the Image.FromFile() method to lock the file如何防止 Image.FromFile() 方法锁定文件
【发布时间】:2013-08-17 12:48:53
【问题描述】:

我正在使用以下代码将 JPG 放入 DataGridView 的图像单元格中。

If strFileName.ToLower.EndsWith(".jpg") Then
     Dim inImg As Image = Image.FromFile(strFileName)
     DataGridView4.Rows.Add()
     DataGridView4.Rows(DataGridView4.Rows().Count - 1).Cells(0).Value = inImg
End If

问题是我需要从程序中保存此文件,但我收到该文件正在被另一个程序使用的消息。

所以我尝试在结束之前添加inImg.Dispose(),但随后程序不再在DataGridView 中显示图像。

如何在DataGridView 中添加图像而不锁定它们?

谢谢

【问题讨论】:

  • 当您说“保存”时,您的意思是对文件执行与程序打开不兼容的操作? (例如:移动、删除、重命名等)?

标签: vb.net image datagridview locking save


【解决方案1】:

当您使用Image.FromFile(strFileName) 方法创建Image 时,该方法会锁定文件,直到您释放Image。确切的原因在下面解释。这就是为什么你不能用这种方法多次访问同一个图像文件。

你可以:

以下是不锁定图像文件的自定义SafeImageFromFile 方法的可能实现:

Public Shared Function SafeImageFromFile(path As String) As Image
    Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
        Dim img = Image.FromStream(fs)
        Return img
    End using
End Function

或者

Public Shared Function SafeImageFromFile(path As String) As Image
    Dim bytes = File.ReadAllBytes(path)
    Using ms As New MemoryStream(bytes)
        Dim img = Image.FromStream(ms)
        Return img
    End Using
End Function

用法

If strFileName.ToLower.EndsWith(".jpg") Then
    Dim inImg As Image = SafeImageFromFile(strFileName)
    Dim index as integer = DataGridView4.Rows.Add()
    DataGridView4.Rows(index).Cells(0).Value = inImg
End If

重要提示

在这里,我使用 Using 语句创建 FileStream 或 MemoryStream 以确保释放流。它在我的系统上运行良好,而且它似乎也适用于你,虽然 MSDN 提到了Image.FromStream(stream) 方法:

您必须在图像的生命周期内保持流打开。

这句话的原因在这里解释一下:KB814675 Bitmap and Image constructor dependencies

GDI+,因此 System.Drawing 命名空间,可能会推迟 解码原始图像位,直到图像需要这些位。 此外,即使图像已被解码,GDI+ 可能 确定丢弃大量内存更有效 位图并稍后重新解码。因此,GDI+ 必须有权访问 位图或图像生命周期中图像的源位 对象。

为了保留对源位的访问,GDI+ 锁定任何源文件,并且 强制应用程序维持任何源流​​的生命周期,对于 位图或图像对象的生命周期。

所以知道上面的代码可能会生成GDIexceptions,因为使用Using 释放流。当您从文件中保存图像或在图像创建过程中可能会发生这种情况。从这个线程 Loading an image from a stream without keeping the stream open 和 Hans Passant 的评论 他们修复了 Vista 版本的 gdiplus.dll 中索引像素格式的几个问题。,这只会在 XP 上发生。

为避免这种情况,您需要保持流打开。方法是:

Public Shared Function SafeImageFromFile(path As String) As Image
    Dim fs As New FileStream(path, FileMode.Open, FileAccess.Read)
    Dim img = Image.FromStream(fs)
    Return img
End Function

或者

Public Shared Function SafeImageFromFile(path As String) As Image
    Dim bytes = File.ReadAllBytes(path)
    Dim ms = New MemoryStream(bytes)
    Dim img = Image.FromStream(ms)
    Return img
End Function

但是最后这些方法有一些缺点,比如不释放流(内存问题),它们违反了规则CA2000 Dispose objects before losing scope。

知识库文章提供了一些解决方法:

创建非索引图像

此方法要求新图像位于非索引像素中 格式(每像素超过 8 位),即使原始图像在 一种索引格式。此解决方法使用 Graphics.DrawImage() 将图像复制到新的 Bitmap 对象的方法:

  1. 从流、内存或文件中构造原始位图。
  2. 创建一个相同大小的新位图,像素格式超过每像素 8 位 (BPP)。
  3. 使用 Graphics.FromImage() 方法获取第二个位图的 Graphics 对象。
  4. 使用 Graphics.DrawImage() 将第一个位图绘制到第二个位图上。
  5. 使用 Graphics.Dispose() 来处理 Graphics。
  6. 使用 Bitmap.Dispose() 处理第一个 Bitmap。

创建索引图像

此解决方法以索引格式创建位图对象:

  1. 从流、内存或文件构造原始位图。
  2. 创建一个与第一个位图具有相同大小和像素格式的新位图。
  3. 使用 Bitmap.LockBits() 方法以原始像素格式锁定两个 Bitmap 对象的整个图像。
  4. 使用 Marshal.Copy 函数或其他内存复制函数将图像位从第一个 Bitmap 复制到第二个 Bitmap。
  5. 使用 Bitmap.UnlockBits() 方法解锁两个 Bitmap 对象。 使用 Bitmap.Dispose() 处理第一个 Bitmap。

这里是非索引图像创建的实现,基于知识库文章和这个答案https://stackoverflow.com/a/7972963/2387010 您最好的选择是创建图像的像素完美副本 - 尽管 YMMV(具有某些类型的图像可能不止一帧,或者您可能还必须复制调色板数据。)但是对于大多数图像,这可行:

Private Shared Function SafeImageFromFile(path As String) As Bitmap
    Dim img As Bitmap = Nothing
    Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
        Using b As New Bitmap(fs)
            img = New Bitmap(b.Width, b.Height, b.PixelFormat)
            Using g As Graphics = Graphics.FromImage(img)
                g.DrawImage(b, Point.Empty)
                g.Flush()
            End Using
        End Using
    End Using
    Return img
End Function

有人指出重要的是FileStream 以读取模式打开(FileAccess.Read)。

是的,但是如果你不使用Using 语句并且你不释放流,或者在多线程上下文中,它更有意义:FileAccess.Write 是不合适的,并且FileAccess.ReadWrite 不是必需的,但如果另一个程序(或您在多线程上下文中的程序)以不同于FileAccess.Read 的其他模式打开文件,则使用FileAccess.Read 模式打开流不会阻止拥有IO.Exception。


如果您希望能够显示图像并同时能够将数据保存到文件中,由于您不使用这些方法锁定文件,因此您应该能够使用Image.Save 方法保存图像(删除/覆盖以前的文件)。

【讨论】:

  • +1 当我第一次看到您的代码时,我并没有意识到它到底在做什么:您还将给定图像的副本添加到 DataGridView!您的代码与来自 OP 的代码之间的最大区别不是“使用”部分,而是从流中获取图像(以读取模式打开)并因此添加它的“虚拟副本”,而不是 Image.FromFile (strFileName) 会(如果您在 Image.FromFile 中添加“使用”,则不会存储图像)。
  • 谢谢,但是你会怎么做呢?我不能说该程序不会在旧系统上使用.. 你会怎么做来显示图像,同时能够将数据保存到文件中?谢谢!
  • @justMe 显示图像并同时能够将数据保存到文件中是什么意思?
  • 更有意义!非常感谢克里斯!答案是完美的!
  • @Chris 我修改了g.DrawImage(b, Point.Empty) 这样g.DrawImage(b, 0, 0, b.width, b.height),因为我的图像模糊了。
【解决方案2】:

@ Chris:使用您的最终代码打开大约 100 个大 (3400x2200) 图像,我在[img = new bitmap(...] 上收到无效参数崩溃,我在打开零尺寸图像之前已经看到了这一点,但这里不是这种情况.我添加了fs.dispose并成功打开了与第一次测试相同大小的数千张相同大小的图像,没有问题。我对你的 cmets 很感兴趣。

Private Function SafeImageFromFile(FilePath As String) As Image
    Dim img As Bitmap = Nothing
    Using fs As New FileStream(FilePath, FileMode.Open, FileAccess.Read)
        Using b As New Bitmap(fs)
            img = New Bitmap(b.Width, b.Height, b.PixelFormat)
            Using g As Graphics = Graphics.FromImage(img)
                g.DrawImage(b, Point.Empty)
                g.Flush()
            End Using
        End Using
        fs.Dispose()
    End Using
    Return img
End Function

【讨论】:

  • 这样效果更好,但在更大的测试中仍然失败。文件流泄漏。
  • call fs.Dispose() 不会在这里改变任何东西,因为下一行 (End Using) 将自动调用 fs.Dispose()。
【解决方案3】:

这没有问题,通过它运行 4189 个 3400x2200 图像(两次)没有问题,这将文件流移到函数之外并重新使用它。我关闭文件以释放写锁。我在循环中将图片框指向此图像以进行测试。

Private fsIMG As FileStream
Private Function SafeImageFromFile(FilePath As String) As Image
    'Ref:  http://stackoverflow.com/questions/18250848/how-to-prevent-the-image-fromfile-method-to-lock-the-file
    Dim img As Bitmap = Nothing
    fsIMG = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
    Using b As New Bitmap(fsIMG)
        img = New Bitmap(b.Width, b.Height, b.PixelFormat)
        Using g As Graphics = Graphics.FromImage(img)
            g.DrawImage(b, Point.Empty)
            g.Flush()
        End Using
    End Using
    fsIMG.Close()
    Return img
End Function

【讨论】:

  • 请编辑您之前的答案,而不是发布新答案。
【解决方案4】:

在网上搜索了很长时间后,我发现我可以使用此代码而没有任何错误。

  Private fsIMG As FileStream
Private Function SafeImageFromFile(FilePath As String) As Image
    'Ref:  http://stackoverflow.com/questions/18250848/how-to-prevent-the-image-fromfile-method-to-lock-the-file
    Dim img As Bitmap = Nothing
    fsIMG = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
    Using b As New Bitmap(fsIMG)
        img = New Bitmap(b.Width, b.Height, b.PixelFormat)
        Using g As Graphics = Graphics.FromImage(img)
            g.DrawImage(b, Point.Empty)
            g.Flush()
        End Using
    End Using
    fsIMG.Close()
    Return img
End Function

【讨论】:

    【解决方案5】:

    我也遇到了同样的情况,用了这段代码:

    ' Create memory stream from file
    Dim ms As New MemoryStream()
    ' Open image file
    Using fs As New FileStream(.FileName, FileMode.Open, FileAccess.Read)
        ' Save to memory stream
        fs.CopyTo(ms)
    End Using
    
    ' Create image from the file's copy in memory
    Dim img = Image.FromStream(ms)
    

    我没有释放内存流,因为它允许稍后使用与原始文件完全相同的编码来保存图像,使用以下代码:

    img.Save(someOtherStream, img.RawFormat)
    

    【讨论】:

      猜你喜欢
      • 2016-11-13
      • 1970-01-01
      • 1970-01-01
      • 2016-08-14
      • 2019-08-14
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      • 2013-08-13
      相关资源
      最近更新 更多