【问题标题】:Can FMX.Graphics.TBitmap.Canvas safely be created by threads?线程可以安全地创建 FMX.Graphics.TBitmap.Canvas 吗?
【发布时间】: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


【解决方案1】:

我向您确认 FMX.Graphics.TBitmap.Canvas 中的代码不是线程安全的。看看它你会发现它是监听消息并且消息不是线程安全的

constructor TD2DBitmapHandle.Create(const AWidth, AHeight: Integer; const AAccess: TMapAccess);
begin
  inherited Create;
  FWidth := AWidth;
  FHeight := AHeight;
  FAccess := AAccess;
  FContextLostId := TMessageManager.DefaultManager.SubscribeToMessage(TContextLostMessage, ContextLostHandler);
end;

【讨论】:

  • 谢谢斯蒂芬。我已经用 TMonitors 修补了使用过的 FMX 代码,它现在运行稳定。
【解决方案2】:

如果您查看 tcontext3d 的代码,您会发现它使用密集的全局变量,因此不能是多线程的! Tcontext3d 用于例如 bitmap.canvas.beginscene

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-29
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    • 2015-02-10
    相关资源
    最近更新 更多