在Delphi中异步线程绘制位图

3
如果许多异步线程在全局TBitmap上绘制,会报错吗?我应该使用关键字来创建我的代码吗?(从我在互联网上的搜索中发现,TBitmap.Draw不是线程安全的)另一个问题:如果许多同步线程在全局TBitmap上绘制,并且VCL Timer从TBitmap异步读取内容,那么这会导致错误吗?谢谢!

你真的需要一个全局TBitmap吗?我猜想,你可能需要多个TBitmap实例。由TTimer渲染的实例不应该是其他线程当前正在写入的实例。 - Martin James
技巧在于视频渲染时,需要保持一组已准备好的 TBitmap 队列,这样 TTimer 就可以始终出队并呈现它,而无需与其他正在准备排队的位图同步。在一个位图实例上使用互斥量、临界区等进行同步将会很混乱,并且性能非常差,即使你能够正确地让它工作。如果你可以避免尽可能多的锁定/同步,那么多线程性能会更安全和更快速——让线程在不同于正在呈现的位图上工作。 - Martin James
1
@Martin,你不能在TTimer上进行视频渲染。你需要比那更精确的东西。 - David Heffernan
@Martin 嗯,我不太确定。我认为你需要一个真正的计时器。 - David Heffernan
如果GUI必须在同步(如临界区)上阻塞,它将无法跟上。 - Martin James
显示剩余3条评论
4个回答

5
是的,您确实需要保护TBitmap免受多个线程的并发访问。临界区对于串行化绘图代码是可以的,但仅此还不够!主线程会缓存GDI资源并定期对其进行清理,这将影响您的TBitmap。因此,在绘制/渲染时,您还需要Lock/Unlock()TBitmap.Canvas以确保VCL不会在背后撤出其资源。

2
我刚刚遇到了这个问题,有点晚了,我知道。是的,如果您从除主线程之外的线程调用任何TBitmap绘图方法,并且不使用Lock/Unlock,则会泄漏GDI句柄,即使每个位图实例对于该线程是私有的,因为存在顽固的全局资源缓存。真的很糟糕,设计真的很差。我的解决方案是使用GR32中的真正线程安全位图,但这可能并不适合所有人。 - David Heffernan

4

由于您的所有线程都修改同一个位图,因此您需要序列化对该位图的所有访问。这意味着读取其内容以及向其写入。

当然,这假定多个线程绘制到共享位图是解决您问题的正确方案。不知道您实际问题是什么,我无法对此发表评论。

更新

由于Remy's answer中描述的问题,绘制位图时还必须使用Lock/Unlock。这应该是此问题的被接受答案。


这是一个不错的选择。另一个选项是同步到主线程。什么最好取决于各种因素。 - David Heffernan
5
如果你在位图的画布上绘图,它有一个使用临界区的锁定方法。 - Sertac Akyuz

1
使用监视器或信号量来控制线程对TBitmap像素做出更改时的操作!

0

你可以使用TThread.Synchronize方法代替吗?

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Classes_TThread_Synchronize@TThread@TThreadMethod.html

根据TThread类的文档,
以下是在使用线程时需要注意的问题和建议:
跟踪太多的线程会消耗CPU时间;在单处理器系统上,推荐限制每个进程的活动线程数为16个。
当多个线程更新相同的资源时,必须进行同步以避免冲突。
大多数访问对象并更新表单的方法必须仅从主线程内调用或使用诸如TMultiReadExclusiveWriteSynchronizer之类的同步对象。

我曾认为如果使用临界区,可以提高我的性能。 - user558126
我正在使用TRTLCriticalSection; - user558126
我没有使用过这个类,但如果它像TCriticalSection一样,那么它就是关于在使用位图之前获取锁定,然后在完成后释放(始终在try/finally块中)。假设TBitmap是全局的,你可能最好定义一个自定义类,其中包含TBitmap和CriticalSection,并使自定义类的实例全局。我不希望它提高性能(因为所有这些线程都需要序列化其访问),但读取/写入将是原子和可靠的。 - EdH
1
全局位图是一个糟糕的计划 - 如果可能的话,操作者应该放弃它。 - Martin James

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接