vb.net 如何防止 Image.FromFile() 方法锁定文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18250848/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 14:40:58  来源:igfitidea点击:

how to prevent the Image.FromFile() method to lock the file

vb.netimagedatagridviewlockingsave

提问by sharkyenergy

I am using following code to put JPG's into a DataGridView's Image cell.

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

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

The problem is that I need to save this file from within the program, but i get the message that the file is beeing used by another program.

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

So i tried to add inImg.Dispose()before the end if, but then the program doesnt display the images anymore in the DataGridView.

所以我试图inImg.Dispose()在结束前添加if,但随后程序不再在DataGridView.

How can i add images in the DataGridViewwithout locking them?

如何在DataGridView不锁定图像的情况下添加图像?

thanks

谢谢

回答by Chris

When you use the Image.FromFile(strFileName)method to create the Image, the method locks the file until you release the Image. The exact reason is explained below. And it's why you can't access more than one time to the same image file with this method.

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

You could instead:

你可以改为:

  • use the Image.FromStream(stream)method.
  • that you use with a NewFileStreamor a MemoryStreamthat you create from the image file.

Here are possible implementation of a custom SafeImageFromFilemethod that doesn't lock the image file:

以下是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

Or

或者

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

Usage

用法

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

Important note

重要的提示

Here I create the FileStreamor a MemoryStreamusing a Usingstatement to make sure the stream is released. It works fine on my system and it seems it work for you too, thoughMSDN says about Image.FromStream(stream)method:

在这里,我创建了FileStream一个MemoryStreamusingUsing语句来确保流被释放。它在我的系统上运行良好,似乎也适用于您,尽管MSDN 说的是Image.FromStream(stream)方法:

You must keep the stream open for the lifetime of the Image.

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

The reason of this sentence is explain here: KB814675 Bitmap and Image constructor dependencies

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

GDI+, and therefore the System.Drawing namespace, may defer the decoding of raw image bits until the bits are required by the image.Additionally, even after the image has been decoded, GDI+ may determine that it is more efficient to discard the memory for a large Bitmap and to re-decode later.Therefore, GDI+ must have access to the source bits for the image for the life of the Bitmap or the Image object.

To retain access to the source bits, GDI+ locks any source file, and forces the application to maintain the life of any source stream, for the life of the Bitmap or the Image object.

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

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

So know the code above could generate GDIexceptionsbecause of releasing the stream using Using. It could happen when you save the image from the file or during the image creation. From this thread Loading an image from a stream without keeping the stream openand Hans Passant's comment they fixed several problems with indexed pixel formats in the Vista version of gdiplus.dll., it would happen only on XP.

所以知道上面的代码可能会GDIexceptions因为使用Using. 当您从文件中保存图像时或在图像创建过程中可能会发生这种情况。从这个线程加载图像而不保持流打开和汉斯帕桑的评论他们修复了 gdiplus.dll 的 Vista 版本中索引像素格式的几个问题。,它只会发生在 XP 上。

To avoid this you need to keep the stream open. The methods would be:

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

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

Or

或者

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

But those last methods have some disadvantage like not releasing the stream (memory issue) and they violate rule CA2000 Dispose objects before losing scope.

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

The KB article gives some workarounds:

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

Create a Non-Indexed Image

This approach requires that the new image be in a non-indexed pixel format (more than 8 bits-per-pixel), even if the original image was in an indexed format. This workaround uses the Graphics.DrawImage() method to copy the image to a new Bitmap object:

  1. Construct the original Bitmap from the stream, from the memory, or from the file.
  2. Create a new Bitmap of the same size, with a pixel format of more than 8 bits-per-pixel (BPP).
  3. Use the Graphics.FromImage() method to obtain a Graphics object for the second Bitmap.
  4. Use Graphics.DrawImage() to draw the first Bitmap onto the second Bitmap.
  5. Use Graphics.Dispose() to dispose of the Graphics.
  6. Use Bitmap.Dispose() to dispose of the first Bitmap.

Create an Indexed Image

This workaround creates a Bitmap object in an indexed format:

  1. Construct the original Bitmap from the stream, from the memory, or from the file.
  2. Create a new Bitmap with the same size and pixel format as the first Bitmap.
  3. Use the Bitmap.LockBits() method to lock the whole image for both Bitmap objects in their native pixel format.
  4. Use either the Marshal.Copy function or another memory copying function to copy the image bits from the first Bitmap to the second Bitmap.
  5. Use the Bitmap.UnlockBits() method to unlock both Bitmap objects. Use Bitmap.Dispose() to dispose of the first Bitmap.

创建非索引图像

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

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

创建索引图像

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

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

Here is an implementation of Non-Indexed Image creation, based on KB article and this answer https://stackoverflow.com/a/7972963/2387010Your best bet is creating a pixel-perfect replica of the image -- though YMMV (with certain types of images there may be more than one frame, or you may have to copy palette data as well.) But for most images, this works:

这是基于知识库文章和此答案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


Someone indicated that what is important is that the FileStreamis opened in read mode (FileAccess.Read).

有人指出,重要的FileStream是在阅读模式下打开 ( FileAccess.Read)。

True, but it makes more sens if you don't use Usingstatement and so you don't release the stream, or in multi threads context: FileAccess.Writeis inappropriate, and FileAccess.ReadWriteis not required, but open the stream with FileAccess.Readmode won't prevent to have an IO.Exceptionif another program (or yours in multi threads context) has opened the file with another mode than FileAccess.Read.

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



If you want to be able to display the image and at the same time be able to save data to the file, Since you don't lock the file with those methods, you should be able to save the image (delete/overwrite the previous file) using the Image.Savemethod.

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

回答by Alan

@ Chris: Opening approximately 100 large (3400x2200) images with your final code, I was receiving an invalid argument crash on [img = new bitmap(...], I have seen this before opening an image of zero size, but that was not the case here. I added fs.disposeand successfully opened thousands of images of the same size of the same set as the first test without issue. I'm interested in your comments on this.

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

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

回答by Alan

This works without issue, ran 4189 images 3400x2200 through it (twice) without issue, this moves the filestream outside of the function and re-uses it. Im closing the file to release the write lock. Im pointing a picturebox at this image in a loop for my test.

这可以正常工作,通过它(两次)运行 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

回答by Newby Programmer

After searching the internet for long time I found out I can use this code without any error.

在互联网上搜索了很长时间后,我发现我可以毫无错误地使用此代码。

  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

回答by Jean-Xavier Bardant

I encountered the same situation and used this code:

我遇到了同样的情况并使用了以下代码:

' 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)

I didn't dispose the memory stream because it allows to save the image later using exactly the same encoding as the original file, using this code:

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

img.Save(someOtherStream, img.RawFormat)