【发布时间】:2019-10-17 15:19:58
【问题描述】:
我正在维护一个使用 FMX.Graphics.TBitmap 的 Delphi 10.2.3 Isapi 应用程序。 多个线程正在创建自己的 private 位图,在其上绘制,将二进制内容返回给 webrequest 处理程序,然后释放位图。 在此堆栈跟踪中发生调试访问冲突时:
:760c4742 KERNELBASE.RaiseException + 0x62
System.DynArraySetLength(nil,$407163,16,$F)
System.DynArraySetLength($6113648,$5DDE84,1,$6B6FE78)
System.Generics.Collections.TListHelper.InternalSetCapacity(8514146)
System.Generics.Collections.TListHelper.InternalGrow(???)
System.Generics.Collections.TListHelper.InternalGrowCheck(???)
System.Generics.Collections.TListHelper.InternalAddManaged((no value))
System.Messaging.TMessageManager.SubscribeToMessage(???,(FMX.Canvas.D2D.TCanvasD2D.ContextLostHandler,$6122F70))
FMX.Canvas.D2D.TCanvasD2D.CreateFromBitmap(???,SystemDefault)
FMX.Graphics.TBitmap.GetCanvas
Unit1.TWorker.Execute
我怀疑FMX框架代码中的这段代码不是线程安全的:
// FMX.Canvas.D2D.pas:
constructor TCanvasD2D.CreateFromBitmap(const ABitmap: TBitmap; const AQuality: TCanvasQuality);
begin
inherited;
FLastBrushTransform := TMatrix.Identity;
CreateResources;
FContextLostId := TMessageManager.DefaultManager.SubscribeToMessage(TContextLostMessage, ContextLostHandler);
end;
它正在调用 singleton TMessageManager.DefaultManager 并将处理程序添加到它的内部字典中,没有任何锁定。这看起来不是很线程安全。 根据文档,当使用 BeginScene 和 EndScene 时,可以在线程中使用 FMX 位图,这很好。 但实际上创建/销毁 FMX 画布似乎不是线程安全的,因为订阅/取消订阅单例默认 MessageManager?这个假设正确吗?
奇怪的是,当任何代码通过断点在调试器中的某个位置暂停和恢复时,它可能只会引发访问冲突。 当程序从不被断点暂停时,它将毫无问题地运行。
【问题讨论】:
-
我在调试多个线程时遇到了很多意外行为。即使根本不使用 FMX 或 VCL。在线程上下文中放置断点是相当冒险的。
-
你可以简单地通过在
TMessageManager.DefaultManager的使用周围添加一个互斥体/关键部分来测试它 -
感谢有关互斥锁的提示。我刚刚制作了 FMX.Canvas.D2D 的副本并将其添加到项目中。添加了带有日志记录的 TMonitor.TryEnter 和 TMonitor.Exit,它现在在 TMessageManager.DefaultManager 上记录了很多竞争条件。
-
所以最终答案是:不,它不是线程安全的,所以当你使用它时,你需要注意线程安全——因为它嵌入到 FMX 框架中,你需要制作那个框架关注这个问题。
-
感谢您的确认,我已经修补了所有使用的 fmx 代码,并且该软件现在可以稳定运行 2 周了。
标签: delphi firemonkey