我应该如何加载图像一次并在多线程 C# 应用程序中传递它?
我正在编写一个应用程序,其目的是在文件夹中查找图像并将其呈现给用户,询问应如何裁剪它们。由于我每天从一种语言跳到另一种语言,我往往会混淆我的最佳实践和概念,这就是这种情况。
程序流程如下:您将看到主窗体,带有一个浏览按钮(您也可以将文件夹拖放到该按钮上)。当您选择一个文件夹时,它会打开另一个表单,扫描所选文件夹中的 .jp[e]g 和 .tif[f] 图像,并将它们显示在列表中,询问您要裁剪其中的哪一个,以及其他一些信息选项。然后它返回到主窗体,该窗体遍历从该窗体中选择的图像列表,并在每个图像的 BackgroundWorker
中将其加载到裁剪窗体中,然后裁剪并保存它。
对于原型设计,我处理图像的加载有点混乱;它加载选择表单的文件夹中的所有图像,然后在裁剪器中再次加载每个选定的图像,然后在主表单中再次加载一次以进行裁剪并保存。对于大图像,这可能会使任务非常耗时,因为在最坏的情况下,此应用程序将处理高达约 4000px2 的 TIFF 源图像。
我想有一种方法可以将每个位图加载一次到某种全局数组中,但这将是 Visual Basic 或 VB for Applications。由于我使用的是 BackgroundWorker,我还必须担心线程安全以及 C# 是否会因为尝试以不安全的方式访问某些内容而对我大喊大叫。关于如何在限制单个图像加载到 Bitmap 对象中的次数的同时实现应用程序的目的,有什么想法吗?
I'm writing an app whose purpose is to find images within a folder and present them to the user asking how they should be cropped. With all of the jumping I do from language to language on a daily basis, I tend to get my best practices and concepts jumbled up, and this is such a case.
Program flow goes like this: you're presented with the main form, with a browse button (that you could alternatively drop a folder onto). When you select a folder, it opens another form that scans the selected folder for .jp[e]g and .tif[f] images and presents them in a list, asking which of them you'd like to crop, and some other options. Then it goes back to the main form which goes through the list of selected images from that form, and within a BackgroundWorker
for each one it loads it into a cropping form, then crops it and saves it.
For prototyping I handle the loading of images a bit messily; it loads all the images in the folder for the selection form, then it loads each selected image again in the cropper one-by-one, and then one more time in the main form to be cropped and saved. For large images, this could make the task time-consuming, as this app would handle TIFF source images up to about 4000px2 in a worst-case scenario.
I would like to think that there is a way to just load each Bitmap
once into some sort of global array, but that would be Visual Basic or VB for Applications. Since I'm using a BackgroundWorker
I also have to worry about thread safety and whether or not C# will yell at me about trying to access something in an unsafe manner. Any ideas as to how I can accomplish the app's purpose while limiting the number of times a single image is loaded into a Bitmap
object?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
因为根据 Microsoft,Bitmap 类实例成员不是保证线程安全我看到两个选项:
高性能但有点风险
将所有内容加载为 Bitmap 对象,并将所有这些 Bitmap 对象添加到
ConcurrentDictionary< /code>
...访问是线程安全的并且速度非常快,因为实现大多是无锁的...
性能稍差,但没有风险
将所有文件加载为
MemoryMappedFile
< /a> 进入ConcurrentDictionary
。当您需要位图时,您只需创建一个MemoryMappedFile
(它将使用您之前创建的onece已经占用的相同内存,因此速度非常快)并让位图从那里加载其内容......当您应用程序关闭或者您想要清理,您只需浏览字典并删除那些MemoryMappedFile
对象...Since according to Microsoft the Bitmap class instance members are not guaranteed to be thread-safe I see 2 options:
performant but a bit risky
Load everything as a Bitmap object and add all those Bitmap objects to a
ConcurrentDictionary
... the access is thread-safe and very fast since the implementation is mostly lock-free...a bit less performant but not risky
Load all files as
MemoryMappedFile
into aConcurrentDictionary
. when you need a Bitmap you just create aMemoryMappedFile
(which will use the same memory already occuppied by the onece you created before thus extremely fast) and let the Bitmap load its content from there... when your app shuts down or you want to cleanup you just go through the Dictionary and get rid of thoseMemoryMappedFile
objects...您有两种选择:一种比另一种更面向对象,但更难实现:
A) 创建一些类(例如,Document、BitmapDocument 或其他类),其中包含一个列表,您可以在其中添加所有图像。不用担心多线程,只需在每次添加图像时锁定列表即可。根据您对位图所做的操作,您可能也需要锁定它们。 (在 C# 中锁定并不难,例如 lock(someVariable){ ... } )在不同的控件使用位图(已经加载到列表中)时制作位图的副本不应该需要太多的内存/时间(因为那些将是 RAM 操作)。当主应用程序必须将此“文档”类传递给所有子控件和表单时,就会出现复杂化。一旦每个控件都知道“文档”实例,对其进行的任何添加都立即可用。
B) 创建一些类,其中列表被标记为内部和静态,以便项目中的任何类都可以查看并使用它。这样你就可以使用一种“全局”数组,但不完全是全局的。它仅在您的项目中可见(由于“内部”标签),并且它仍然保存在类中。请注意,您仍然需要使用 C# 的锁定机制来使所有内容都是线程安全的。
请注意,您可以考虑编写方法来操作它们并适当锁定对象,而不是直接访问类成员。 (这将是正确的编程实践。)对于 B,只需创建标记为“内部”和“静态”的方法。
我个人会使用 B(实现和调试速度更快),特别是对于原型设计,因为使用它使得将来转向 A 不会太困难。
此外,如果您使用(B)的内部静态对象和函数的方法,您可以编写一些像 GetBitmap(string path) 这样的函数,它从磁盘加载图像,并且还保存所有最近加载的图像的缓冲区,这样线程就不会必须从磁盘加载两次。
You have two options: one more object oriented than the other but harder to implement:
A) Create some class (eg, Document, BitmapDocument, or whatever) that holds a List where you add all of your images. Don't worry about multithreading, just lock the List every time you add an image. Depending on what you do with the Bitmaps, you might need to lock them too. (locking is not hard in c#, eg lock(someVariable){ ... } ) Making copies of the Bitmaps (that are already loaded into the List inside) as different controls use them should not require too much memory/time (since those would be RAM operations). The complication arises when the main application has to pass this "document" class to all child controls and forms. Once every control knows the "document" instance, any additions to it are immediately available.
B) Create some class where the List is marked internal and static, such that any class in your project can view it and use it. This way you are using a sort of "global" array, but not quite global. It is only visible inside your project (due to the "internal" tag), and it is still held inside a class. Note that you'll still need to use C#'s locking mechanism to make everything thread-safe.
Note that instead of accessing class members directly, you might consider writing methods to manipulate them and lock the objects appropriately. (This would be the correct programming practice.) For B, just create methods that are marked "internal" and "static".
I'd personally use B (muck quicker to implement and debug), especially for prototyping, and since using it makes it not too hard to move on to A in the future.
Furthermore, if you use (B)'s approach of internal static objects and functions, you can write some function like GetBitmap(string path) that loads images from disk and also holds a buffer of all recently loaded images such that threads don't have to load from disk twice.